Skip to content
Permalink
Browse files
feat(assertions): Expectation aliases via Expectation.to(description)…
….soThatActual(expectation)
  • Loading branch information
jan-molak committed May 1, 2019
1 parent f30647b commit d4b8c48580fde053b16305a7727af98bb05b7480
@@ -1,10 +1,9 @@
import 'mocha';
import { given } from 'mocha-testdata';

import { expect, stage } from '@integration/testing-tools';
import { Answerable, AssertionError } from '@serenity-js/core';
import { Ensure, Expectation } from '../../src';
import { isIdenticalTo, p, q } from '../fixtures';
import 'mocha';
import { given } from 'mocha-testdata';
import { and, Ensure, equals, Expectation, isGreaterThan, isLessThan, or } from '../src';
import { isIdenticalTo, p, q } from './fixtures';

/** @test {Expectation} */
describe('Expectation', () => {
@@ -45,4 +44,29 @@ describe('Expectation', () => {
)).to.be.fulfilled;
});
});

describe('allows to alias an expectation, so that the alias', () => {

function isWithin(lowerBound: number, upperBound: number) {
return Expectation
.to(`have value within ${ lowerBound } and ${ upperBound }`)
.soThatActual(and(
or(isGreaterThan(lowerBound), equals(lowerBound)),
or(isLessThan(upperBound), equals(upperBound)),
));
}

/** @test {Expectation.to} */
it('contributes to a human-readable description', () => {
expect(Ensure.that(5, isWithin(3, 6)).toString())
.to.equal(`#actor ensures that 5 does have value within 3 and 6`);
});

/** @test {Expectation.to} */
it('provides a precise failure message when the expectation is not met', () => {
return expect(Astrid.attemptsTo(
Ensure.that(9, isWithin(7, 8)),
)).to.be.rejectedWith(AssertionError, `Expected 9 to have value that's less than 8 or equal 8`);
});
});
});
@@ -18,19 +18,28 @@ export abstract class Expectation<Expected, Actual = Expected>
});
}

static to<A>(relationshipName: string): {
soThatActual: (...expectations: Array<Expectation<any, A>>) => Expectation<any, A>,
} {
return {
soThatActual: (expectation: Expectation<any, A>): Expectation<any, A> => {
return new ExpectationAlias<A>(relationshipName, expectation);
},
};
}

abstract answeredBy(actor: AnswersQuestions): (actual: Actual) => Promise<Outcome<Expected, Actual>>;

abstract toString(): string;
}

class DynamicallyGeneratedExpectation<Expected, Actual> extends Expectation<Expected, Actual> {
class DynamicallyGeneratedExpectation<Expected, Actual> implements Expectation<Expected, Actual> {

constructor(
private readonly description: string,
private readonly statement: (actual: Actual, expected: Expected) => boolean,
private readonly expectedValue: Answerable<Expected>,
) {
super();
}

answeredBy(actor: AnswersQuestions): (actual: Actual) => Promise<Outcome<Expected, Actual>> {
@@ -47,3 +56,24 @@ class DynamicallyGeneratedExpectation<Expected, Actual> extends Expectation<Expe
return `${ this.description } ${ formatted `${this.expectedValue}` }`;
}
}

class ExpectationAlias<Actual> implements Expectation<any, Actual> {

constructor(
private readonly description: string,
private readonly expectation: Expectation<any, Actual>,
) {
}

answeredBy(actor: AnswersQuestions): (actual: Actual) => Promise<Outcome<any, Actual>> {

return (actual: Actual) =>
this.expectation.answeredBy(actor)(actual).then(_ => _ instanceof ExpectationMet
? new ExpectationMet(this.description, _.expected, _.actual)
: new ExpectationNotMet(_.message, _.expected, _.actual));
}

toString(): string {
return this.description;
}
}
@@ -6,8 +6,8 @@ import { promiseOf } from '../promiseOf';
/**
* @access private
*/
export class ElementFinderExpectation extends Expectation<boolean, ElementFinder> {
static to(message: string, fn: (actual: ElementFinder) => PromiseLike<boolean>): Expectation<boolean, ElementFinder> {
export class ElementFinderExpectation extends Expectation<any, ElementFinder> {
static forElementTo(message: string, fn: (actual: ElementFinder) => PromiseLike<boolean>): Expectation<any, ElementFinder> {
return new ElementFinderExpectation(message, fn);
}

@@ -22,8 +22,8 @@ export class ElementFinderExpectation extends Expectation<boolean, ElementFinder

return (actual: ElementFinder) =>
promiseOf(this.fn(actual)).then(_ => _
? new ExpectationMet(this.toString(), true, actual)
: new ExpectationNotMet(this.toString(), true, actual),
? new ExpectationMet(this.toString(), null, actual)
: new ExpectationNotMet(this.toString(), null, actual),
);
}

This file was deleted.

@@ -1,6 +1,5 @@
import { Expectation } from '@serenity-js/assertions';
import { and, Expectation } from '@serenity-js/assertions';
import { ElementFinder } from 'protractor';
import { combined } from './combined';
import { isEnabled } from './isEnabled';
import { isVisible } from './isVisible';

@@ -10,6 +9,6 @@ import { isVisible } from './isVisible';
*
* @returns {Expectation<boolean, ElementFinder>}
*/
export function isClickable(): Expectation<boolean, ElementFinder> {
return combined('become clickable', isVisible(), isEnabled());
export function isClickable(): Expectation<any, ElementFinder> {
return Expectation.to<ElementFinder>('become clickable').soThatActual(and(isVisible(), isEnabled()));
}
@@ -9,5 +9,5 @@ import { ElementFinderExpectation } from './ElementFinderExpectation';
* @returns {Expectation<boolean, ElementFinder>}
*/
export function isEnabled(): Expectation<boolean, ElementFinder> {
return ElementFinderExpectation.to('become enabled', actual => actual.isEnabled());
return ElementFinderExpectation.forElementTo('become enabled', actual => actual.isEnabled());
}
@@ -10,5 +10,5 @@ import { ElementFinderExpectation } from './ElementFinderExpectation';
* @returns {Expectation<boolean, ElementFinder>}
*/
export function isPresent(): Expectation<boolean, ElementFinder> {
return ElementFinderExpectation.to('become present', actual => actual.isPresent());
return ElementFinderExpectation.forElementTo('become present', actual => actual.isPresent());
}
@@ -1,6 +1,5 @@
import { Expectation } from '@serenity-js/assertions';
import { and, Expectation } from '@serenity-js/assertions';
import { ElementFinder } from 'protractor';
import { combined } from './combined';
import { ElementFinderExpectation } from './ElementFinderExpectation';
import { isPresent } from './isPresent';

@@ -10,6 +9,9 @@ import { isPresent } from './isPresent';
*
* @returns {Expectation<boolean, ElementFinder>}
*/
export function isSelected(): Expectation<boolean, ElementFinder> {
return combined('become selected', isPresent(), ElementFinderExpectation.to('become selected', actual => actual.isSelected()));
export function isSelected(): Expectation<any, ElementFinder> {
return Expectation.to<ElementFinder>('become selected').soThatActual(and(
isPresent(),
ElementFinderExpectation.forElementTo('become selected', actual => actual.isSelected()),
));
}
@@ -1,6 +1,5 @@
import { Expectation } from '@serenity-js/assertions';
import { and, Expectation } from '@serenity-js/assertions';
import { ElementFinder } from 'protractor';
import { combined } from './combined';
import { ElementFinderExpectation } from './ElementFinderExpectation';
import { isPresent } from './isPresent';

@@ -10,10 +9,13 @@ import { isPresent } from './isPresent';
*
* @returns {Expectation<boolean, ElementFinder>}
*/
export function isVisible(): Expectation<boolean, ElementFinder> {
return combined('become visible', isPresent(), isDisplayed());
export function isVisible(): Expectation<any, ElementFinder> {
return Expectation.to<ElementFinder>('become visible').soThatActual(and(
isPresent(),
isDisplayed(),
));
}

function isDisplayed(): Expectation<boolean, ElementFinder> {
return ElementFinderExpectation.to('become displayed', actual => actual.isDisplayed());
function isDisplayed(): Expectation<any, ElementFinder> {
return ElementFinderExpectation.forElementTo('become displayed', actual => actual.isDisplayed());
}

0 comments on commit d4b8c48

Please sign in to comment.