import { Answerable, AnswersQuestions, AssertionError, CollectsArtifacts, Interaction, LogicError, RuntimeError, UsesAbilities } from '@serenity-js/core'; import { formatted } from '@serenity-js/core/lib/io'; import { inspected } from '@serenity-js/core/lib/io/inspected'; import { Artifact, Name, TextData } from '@serenity-js/core/lib/model'; import { match } from 'tiny-types'; import { Expectation } from './Expectation'; import { ExpectationMet, ExpectationNotMet, Outcome } from './outcomes'; export class Ensure extends Interaction { static that(actual: Answerable, expectation: Expectation): Ensure { return new Ensure(actual, expectation); } constructor( protected readonly actual: Answerable, protected readonly expectation: Expectation, ) { super(); } performAs(actor: UsesAbilities & AnswersQuestions & CollectsArtifacts): PromiseLike { return Promise.all([ actor.answer(this.actual), actor.answer(this.expectation), ]). then(([ actual, expectation ]) => expectation(actual).then(outcome => match, void>(outcome) .when(ExpectationNotMet, o => { actor.collect(this.artifactFrom(actual), new Name(`Actual value`)); throw this.errorForOutcome(o); }) .when(ExpectationMet, _ => void 0) .else(o => { throw new LogicError(formatted `An Expectation should return an instance of an Outcome, not ${ o }`); }), ), ); } toString(): string { return formatted `#actor ensures that ${ this.actual } does ${ this.expectation }`; } otherwiseFailWith(typeOfRuntimeError: new (message: string, cause?: Error) => RuntimeError, message?: string): Interaction { return new EnsureOrFailWithCustomError(this.actual, this.expectation, typeOfRuntimeError, message); } protected errorForOutcome(outcome: Outcome): RuntimeError { return this.asAssertionError(outcome); } protected asAssertionError(outcome: Outcome) { return new AssertionError( `Expected ${ formatted`${ this.actual }` } to ${ outcome.message }`, outcome.expected, outcome.actual, ); } private artifactFrom(actual: Actual): Artifact { return TextData.fromJSON({ contentType: 'text/plain', data: inspected(actual), }); } } /** * @package */ class EnsureOrFailWithCustomError extends Ensure { constructor( actual: Answerable, expectation: Expectation, private readonly typeOfRuntimeError: new (message: string, cause?: Error) => RuntimeError, private readonly message?: string, ) { super(actual, expectation); } protected errorForOutcome(outcome: Outcome): RuntimeError { const assertionError = this.asAssertionError(outcome); return new this.typeOfRuntimeError(this.message || assertionError.message, assertionError); } }