All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.specs2.specification.script.GWT.scala Maven / Gradle / Ivy

There is a newer version: 4.10.6
Show newest version
package org.specs2
package specification
package script

import core.{Env, Fragment, Fragments}
import create.{FragmentFactory, FragmentsFactory}
import shapeless.{HList, HNil, ::}
import shapeless.ops.hlist.{ToTraversable, ToList}
import execute._
import ResultLogicalCombinators._
import scala.collection.mutable
import scalaz.syntax.std.list._

/**
 * The GWT trait can be used to associate a piece of text to Given/When/Then steps according to the [BDD](http://en.wikipedia.org/wiki/Behavior-driven_development)
 * way of describing acceptance criteria
 */
trait GWT extends StepParsers with Scripts { outer: FragmentsFactory =>
  private val factory: FragmentFactory = fragmentFactory; import factory._

  /** renaming of the shapeless cons object to avoid imports */
  val :: = shapeless.::

  /**
   * start a sequence of GWT steps
   * by default the scenario template that is used is considering the last lines before the scenario end
   *  to form the given/when/then steps
   */
  object Scenario {
    def apply(title: String)(implicit template: ScriptTemplate[Scenario, GivenWhenThenLines] = LastLinesScriptTemplate()): GWTStart = GWTStart(title, template)
  }

  private lazy val allAsString = new StepParser[String] {
    def parse(text: String) = Right((text, text))
    def run(text: String) = Right(text)
    def strip(text: String) = text
  }


  /**
   * Start of a scenario
   */
  case class GWTStart(title: String, template: ScriptTemplate[Scenario, GivenWhenThenLines], isStart: Boolean = true) extends Scenario {
    type S = GWTStart

    def given[T](f: StepParser[T]) = GWTGivens[T :: HNil, (StepParser[T]) :: HNil, T](title, template, f :: HNil)
    def given(): GWTGivens[String :: HNil, (StepParser[String]) :: HNil, String] = given(allAsString)
    def when() = GWTGivens[HNil, HNil, Unit](title, template, HNil).when()
    def when[R, U](value: =>R)(implicit lub: ToList[R :: HNil, U]) = GWTGivens[HNil, HNil, Unit](title, template, HNil).when().apply[R, U] { case _ => value }

    def fragments(text: String): Fragments = Fragments(factory.text(text), factory.break)

    def start: Scenario = this
    def end: Scenario = this
    def withTitle(t: String) = copy(title = t)

    def stepsNumbers = Seq()
  }

  /**
   * GT = Given types, types of extracted values for then steps
   * GTE = Given extractor types, types of extractor functions
   * WT = When types, types of extracted values for when steps
   * WTR = When result types, after mapping
   * WTE = When extractor types
   * WM = Mapping functions types
   * TTE = Then types extractors
   * VE =  verification types for then steps
   */
  case class GWTGivens[GT <: HList, GTE <: HList, GTU](title: String, template: ScriptTemplate[Scenario, GivenWhenThenLines], givenExtractors: GTE = HNil, isStart: Boolean = true) extends Scenario {
    type S = GWTGivens[GT, GTE, GTU]

    def given[T, U](f: StepParser[T])(implicit lub: ToList[T :: GT, U]) = GWTGivens[T :: GT, (StepParser[T]) :: GTE, U](title, template, f :: givenExtractors)
    def given[U]()(implicit lub: ToList[String :: GT, U]): GWTGivens[String :: GT, (StepParser[String]) :: GTE, U] = given(allAsString)
    def when[T](f: StepParser[T]) = GWTWhensApply[T, GT, GTE, GTU, T :: HNil, HNil, (StepParser[T]) :: HNil, HNil, Any](this, f :: HNil, HNil)
    def when(): GWTWhensApply[String, GT, GTE, GTU, String :: HNil, HNil, (StepParser[String]) :: HNil, HNil, Any] = when(allAsString)

    def fragments(text: String): Fragments = Fragments(factory.text(text), factory.break)

    def start = copy(isStart = true)
    def end   = copy(isStart = false)
    def withTitle(t: String) = copy(title = t)

    def stepsNumbers = Seq(givenExtractors.toList.size)
  }

  case class GWTWhens[GT <: HList, GTE <: HList, GTU, WT <: HList, WTR <: HList, WTE <: HList, WM <: HList, WMU](givens: GWTGivens[GT, GTE, GTU], whenExtractors: WTE, mappers: WM, isStart: Boolean = true) extends Scenario {
    type S = GWTWhens[GT, GTE, GTU, WT, WTR, WTE, WM, WMU]

    def when[T](f: StepParser[T]) = GWTWhensApply[T, GT, GTE, GTU, T :: WT, WTR, (StepParser[T]) :: WTE, WM, WMU](givens, f :: whenExtractors, mappers)
    def when(): GWTWhensApply[String, GT, GTE, GTU, String :: WT, WTR, (StepParser[String]) :: WTE, WM, WMU] = when(allAsString)
    def when[R, U](value: =>R)(implicit lub: ToList[R :: WTR, U]): GWTWhens[GT, GTE, GTU, String :: WT, R :: WTR, (StepParser[String]) :: WTE, Mapper[String,GT,Nothing,R] :: WM, U]  =
      when().apply[R, U] { case _ => value }

    def andThen[T](f: StepParser[T]) = GWTThensApply[T, GT, GTE, GTU, WT, WTR, WTE, WM, WMU, (StepParser[T]) :: HNil, HNil](GWTWhens(givens, whenExtractors, mappers), f :: HNil, HNil)
    def andThen(): GWTThensApply[String, GT, GTE, GTU, WT, WTR, WTE, WM, WMU, (StepParser[String]) :: HNil, HNil] = andThen(allAsString)

    def title = givens.title
    def fragments(text: String): Fragments = Fragments(factory.text(text), factory.break)

    def start = copy(isStart = true)
    def end   = copy(isStart = false)
    def withTitle(t: String) = copy(givens = givens.withTitle(t))

    def stepsNumbers = givens.stepsNumbers :+ whenExtractors.toList.size
  }

  case class Mapper[T, P <: HList, U, R](f1: Option[(T :: P) => R] = None, f2: Option[(T, Seq[U]) => R] = None) {
    def apply(t: T, p: Either[P, Seq[U]]): R = if (f1.isDefined) f1.get(t :: p.left.get) else f2.get(t, p.right.get)
  }

  case class VerifyFunction[T, P <: HList, U, R : AsResult](f1: Option[(T :: P) => R] = None, f2: Option[(T, Seq[U]) => R] = None) {
    def apply(t: T, p: Either[P, Seq[U]]): Result = AsResult(if (f1.isDefined) f1.get(t :: p.left.get) else f2.get(t, p.right.get))
  }

  case class GWTWhensApply[T, GT <: HList, GTE <: HList, GTU,
                              WT <: HList, WTR <: HList, WTE <: HList, WM <: HList, WMU](givens: GWTGivens[GT, GTE, GTU], whenExtractors: WTE, mappers: WM) {

    def apply[R, U](map: (T :: GT) => R)(implicit lub: ToList[R :: WTR, U]) =
      GWTWhens[GT, GTE, GTU, WT, R :: WTR, WTE, Mapper[T, GT, Nothing, R] :: WM, U](givens, whenExtractors, Mapper[T, GT, Nothing, R](f1 = Some(map)) :: mappers)

    def collect[R, U](map: (T, Seq[GTU]) => R)(implicit lub: ToList[R :: WTR, U]) =
      GWTWhens[GT, GTE, GTU, WT, R :: WTR, WTE, Mapper[T, HNil, GTU, R] :: WM, U](givens, whenExtractors, Mapper[T, HNil, GTU, R](f2 = Some(map)) :: mappers)
  }

  case class GWTThens[GT <: HList, GTE <: HList, GTU,
                      WT <: HList, WTR <: HList, WTE <: HList, WM <: HList, WTU,
                      TTE <: HList, VE <: HList](whens: GWTWhens[GT, GTE, GTU, WT, WTR, WTE, WM, WTU],
                                                 thenExtractors: TTE, verifications: VE, isStart: Boolean = true) extends Scenario {
    type S = GWTThens[GT, GTE, GTU, WT, WTR, WTE, WM, WTU, TTE, VE]

    def andThen[T](f: StepParser[T]) =
      GWTThensApply[T, GT, GTE, GTU, WT, WTR, WTE, WM, WTU, StepParser[T] :: TTE, VE](GWTWhens(whens.givens, whens.whenExtractors, whens.mappers), f :: thenExtractors, verifications)

    def andThen(): GWTThensApply[String, GT, GTE, GTU, WT, WTR, WTE, WM, WTU, StepParser[String] :: TTE, VE] = andThen(allAsString)

    def title = whens.title
    def givens = whens.givens
    def template = givens.template

    /**
     * use the template to parse the text and return blocks of TextLines, GivenLines, WhenLines, ThenLines
     *
     * For each of this blocks create steps and examples with execution dependencies (values created in a steps and used in
     * another one).
     */
    def fragments(text: String): Fragments = {

      // results of given and when steps, stored in the reverse order of definition
      var givenSteps: Seq[Fragment] = Seq()
      var whenSteps: Seq[Fragment]  = Seq()

      lazy val givenValues = stepsValues(givenSteps)
      lazy val givenResult = result(givenSteps)
      lazy val whenValues = stepsValues(whenSteps)
      lazy val whenResult = result(whenSteps)

      template.lines(text, this).lines.foldLeft(Fragments()) { (fs, lines) =>
        lines match {
          // Text lines are left untouched
          case TextLines(ls) =>
            fs append factory.text(ls) append break

          // Given lines must create steps with extracted values
          case GivenLines(ls) =>
            givenSteps = (givenExtractorsList zip ls).map { case (extractor, line) => factory.step(extractLine(extractor, line)) }.reverse
            fs append breaks(2) append appendSteps(givenExtractorsList, ls, givenSteps)

          // When lines must create steps with extracted values, and map them using given values
          case WhenLines(ls) =>

            whenSteps = (whenExtractorsList zip ls zip whenMappersList).map { case ((extractor, line), mapper) =>
              factory.step(execute(givenResult, extractor, line) { t: Any =>
                val map = mapper.asInstanceOf[Mapper[Any, HList, Any, Any]]
                DecoratedResult(map(t, if (map.f1.isDefined) Left(givenValues) else Right(givenValues.toList)), Success())
              })
            }.reverse

            fs append appendSteps(whenExtractorsList, ls, whenSteps)

          // Then lines must create examples with previous when values
          case ThenLines(ls) =>
            val thenExamples: List[Fragment] = (thenExtractorsList zip ls zip verificationsList).map { case ((extractor: StepParser[_], line), verify) =>
              example(extractor.strip(line),
                execute(givenResult and whenResult, extractor, line) { t: Any =>
                  val verifyFunction = verify.asInstanceOf[VerifyFunction[Any, HList, Any, Any]]
                  verifyFunction(t, if (verifyFunction.f1.isDefined) Left(whenValues) else Right(whenValues.toList))
                }.asInstanceOf[Result])
            }
            fs append thenExamples.intersperse(factory.break)
        }
      }
    }


    def start = copy(isStart = true)
    def end   = copy(isStart = false)
    def withTitle(t: String) = copy(whens = whens.withTitle(t))

    def stepsNumbers = whens.stepsNumbers :+ thenExtractors.toList.size

    /**
     * utility methods to build steps
     * extractors, mappers and verifications are sorted in the order of definition
     */
    private def givenExtractorsList = givens.givenExtractors.toList.reverse.map(_.asInstanceOf[StepParser[_]])
    private def whenExtractorsList  = whens.whenExtractors.toList.reverse.map(_.asInstanceOf[StepParser[_]])
    private def whenMappersList     = whens.mappers.toList.reverse
    private def thenExtractorsList  = thenExtractors.toList.reverse.map(_.asInstanceOf[StepParser[_]])
    private def verificationsList   = verifications.toList.reverse

    /** @return the values of all executed steps */
    private def stepsValues(steps: Seq[Fragment]) = steps.foldRight(HNil: HList) { (cur, res) =>
      value(cur.execution.execute(Env()).result) :: res
    }
    /** @return a -and- on all the execution results of steps */
    private def result(steps: Seq[Fragment]) = steps.foldRight(Success(): Result) { (cur, res) =>
      cur.execution.execute(Env()).result and res
    }
    /** execute a block of code depending on a previous result */
    private def executeIf(result: Result)(value: =>Any) = if (result.isSuccess) value else Skipped(" ")

    /** zip extractors, lines and steps to intercalate steps between texts fragments (and strip the lines of their delimiters if any */
    private def appendSteps(extractors: Seq[StepParser[_]], lines: Seq[String], steps: Seq[Fragment]): Seq[Fragment] =
      (extractors zip lines zip steps).map {
        case ((extractor: StepParser[_], l: String), s: Fragment) =>
          Seq(factory.text(extractor.strip(l)), break, s)
      }.flatten

    /** extract values from a line and execute a function */
    private def execute(previousResult: Result, extractor: StepParser[_], line: String)(f: Any => Any) =
      executeIf(previousResult) {
        extractLine(extractor, line) match {
          case DecoratedResult(t, _) => f(t)
          case other                 => other
        }
      }
    /** extract values from a line */
    private def extractLine(extractor: Any, line: String): Result = {
      extractor.asInstanceOf[StepParser[Any]].parse(line).fold(e => Error(e), t => DecoratedResult(t._2, Success()))
    }

    private def breaks(n: Int): Fragments = Fragments((1 to n).map(i => factory.break):_*)
  }

  case class GWTThensApply[T, GT <: HList, GTE <: HList, GTU,
                              WT <: HList, WTR <: HList, WTE <: HList, WM <: HList, WMU,
                              TTE <: HList, VE <: HList](whens: GWTWhens[GT, GTE, GTU, WT, WTR, WTE, WM, WMU],
                                                         thenExtractors: TTE, verifications: VE) {

    def apply[R : AsResult](verify: (T :: WTR) => R) =
      GWTThens[GT, GTE, GTU, WT, WTR, WTE, WM, WMU, TTE, VerifyFunction[T, WTR, Nothing, R] :: VE](whens, thenExtractors, VerifyFunction[T, WTR, Nothing, R](f1 = Some(verify)) :: verifications)

    def collect[R : AsResult](verify: (T, Seq[WMU]) => R) =
      GWTThens[GT, GTE, GTU, WT, WTR, WTE, WM, WMU, TTE, VerifyFunction[T, HNil, WMU, R] :: VE](whens, thenExtractors, VerifyFunction[T, HNil, WMU, R](f2 = Some(verify)) :: verifications)

  }

  private implicit def toListAny[H <: HList]: ToList[H, Any] = new ToTraversable[H, List] {
    type Lub = Any
    def builder(): mutable.Builder[Lub, List[Lub]] = new scala.collection.mutable.ListBuffer[Lub]
    def append[LLub](list: H, b: mutable.Builder[LLub, List[LLub]], f: Lub => LLub): Unit = {
      def add[T <: HList](list: T): Unit =
        list match {
          case head :: HNil => b.+=(f(head)); ()
          case head :: tail => b.+=(f(head)); add(tail)
          case _            => ()
        }
      add(list)
    }
  }

  private def value(r: Result) = r match {
    case DecoratedResult(v, _) => v
    case _ => r
  }

}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy