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

org.specs2.mutable.FragmentsBuilder.scala Maven / Gradle / Ivy

There is a newer version: 3.7
Show newest version
package org.specs2
package mutable

import execute._
import main._
import text.RegexExtractor
import RegexExtractor._
import specification.{FormattingFragments => FF, _}
import StandardResults._
import control.ImplicitParameters
import control.Functions._

/**
 * Adding new implicits to support specs-like naming: "the system" should "do this" in { ... }
 *
 * This trait belongs to the mutable package because it works by mutating a local variable each time a new Fragment
 * is created.
 *
 */
trait FragmentsBuilder extends specification.FragmentsBuilder
  with ExamplesFactory
  with SideEffectingCreationPaths
  with ImplicitParameters {

  /** local mutable contents of the specification */
  protected[mutable] var specFragments: Fragments = Fragments.createList(FF.br)
  protected[specs2] def fragments: Fragments = { replay; specFragments }

  /** @return a Fragments object from a single piece of text */
  override implicit def textFragment(s: String): FragmentsFragment = {
    val t = textStart(s)
    addFragments(t)
    t
  }

  implicit def text(s: String): MutableSpecText = new MutableSpecText(s)
  class MutableSpecText(s: String) {
    def txt = textFragment(s)
  }

  override implicit def title(s: String): MutableSpecTitle = new MutableSpecTitle(s)
  class MutableSpecTitle(s: String) extends SpecTitle(s) {
    override def title = addFragments(super.title)
  }

  implicit def described(s: String): Described = new Described(s)
  class Described(s: String) {
    def should(fs: =>Fragment) = addFragments(s, fs, "should")
    def can(fs: =>Fragment)    = addFragments(s, fs, "can")

    def should(fs: =>Fragments)(implicit p: ImplicitParam) = addFragments(s, fs, "should")
    def can(fs: =>Fragments)(implicit p: ImplicitParam)    = addFragments(s, fs, "can")

    def should(fs: =>Unit)(implicit p1: ImplicitParam1, p2: ImplicitParam2) = addFragments(s, examplesBlock(fs), "should")
    def can(fs: =>Unit)   (implicit p1: ImplicitParam1, p2: ImplicitParam2) = addFragments(s, examplesBlock(fs), "can")
  }
  /**
   * add a new example using 'in' or '>>' or '!'
   */
  implicit def inExample(s: String): InExample = new InExample(s)
  /** transient class to hold an example description before creating a full Example */
  class InExample(s: String) {
    def in[T : AsResult](r: =>T): Example = {
      val example = exampleFactory.newExample(s, r)
      example
    }
    def >>[T : AsResult](r: =>T): Example = in(r)

    def in[T : AsResult](f: String => T): Example = exampleFactory.newExample(s, f(s))
    def >>[T : AsResult](f: String => T): Example = in(f)

    def in(block: =>NameSpace)(implicit p1: ImplicitParam1,
                                        p2: ImplicitParam2): Fragments  = addSideEffectingBlock(block)
    def >>(block: =>NameSpace)(implicit p1: ImplicitParam1,
                                        p2: ImplicitParam2): Fragments = in(block)(p1, p2)

    def in(block: =>Fragment)(implicit p: ImplicitParam): Fragments = addSideEffectingBlock2(block)
    def >>(block: =>Fragment)(implicit p: ImplicitParam): Fragments = in(block)(p)

    def in(fs: =>Fragments): Fragments = addSideEffectingBlock(fs)
    def >>(fs: =>Fragments): Fragments = addSideEffectingBlock(fs)

    private def addSideEffectingBlock[T](block: =>T) = {
      addFragments(FF.br)
      addFragments(s)
      executeBlock(block)
      addFragments(FF.bt)
    }
    private def addSideEffectingBlock2[T](block: =>T) = {
      addFragments(FF.br)
      addFragments(s)
      addFragments(FF.br)
      executeBlock(block)
      addFragments(FF.bt)
    }
  }

  /** add a block of examples */
  def examplesBlock(u: =>Unit) = {
    executeBlock(u)
    FF.t(0)
  }
  /**
   * adding a conflicting implicit to warn the user when a `>>` was forgotten
   */
  implicit def `***If you see this message this means that you've forgotten an operator after the description string: you should write "example" >> result ***`(s: String): WarningForgottenOperator = new WarningForgottenOperator(s)
  class WarningForgottenOperator(s: String) {
    def apply[T : AsResult](r: =>T): Example = sys.error("there should be a compilation error!")
  }


    /**
   *  add a new action to the Fragments
   */
  def action(a: =>Any) = {
    val newAction = Action(a)
    addFragments(newAction)
    newAction
  }
  /**
   * add a new step to the Fragments
   */
  def step(a: =>Any, global: Boolean = false) = {
    val newStep = Step(a).copy(isolable = !global)
    addFragments(newStep)
    newStep
  }
  /**
   * add a new stopOnFail step to the Fragments
   */
  def stopOnFail(when: =>Boolean = true): Step = {
    val newStep = Step.stopOnFail(when)
    addFragments(newStep)
    newStep
  }

  /**
   * add a new link to the Fragments
   */
  override def link(fss: Seq[Fragments]): Fragments               = addFragments(super.link(fss))
  override def link(htmlLink: HtmlLink, fs: Fragments): Fragments = addFragments(super.link(htmlLink, fs))
  override def see(fss: Seq[Fragments]): Fragments                = addFragments(super.see(fss))
  override def see(htmlLink: HtmlLink, fs: Fragments): Fragments  = addFragments(super.see(htmlLink, fs))

  protected def addFragments[T](s: String, fs: =>T, word: String): Fragments = {
    addFragments(FF.br)
    addFragments(s + " " + word)
    addFragments(FF.br)
    executeBlock(fs)
    addFragments(FF.bt)
  }

  protected def addFragments(fs: Fragments): Fragments = {
    val element = fs.middle.lastOption.getOrElse(Text("root"))
    addBlockElement(element)
    element match {
      case e: Example => updateSpecFragments(fragments => new FragmentsFragment(fragments) ^ e.creationPathIs(creationPath))
      case a: Action  => updateSpecFragments(fragments => new FragmentsFragment(fragments) ^ a.creationPathIs(creationPath))
      case other      => updateSpecFragments(fragments => new FragmentsFragment(fragments) ^ fs)
    }
    fs
  }
  protected def addFragments(fs: Seq[Fragment]): Fragments = {
    addFragments(Fragments.createList(fs:_*))
  }
  protected def addArguments(a: Arguments): Arguments = {
    updateSpecFragments(fragments => new FragmentsFragment(fragments) ^ a)
    a
  }

  protected def updateSpecFragments(f: Fragments => Fragments) = {
    effect(specFragments = f(specFragments))
  }

  protected def addExample(ex: =>Example): Example = {
    val example = ex
    addFragments(Fragments.createList(example))
    addFragments(Fragments.createList(FF.br))
    example
  }

}

/**
 * allow to write "this example has several expectations" in { Seq(1, 2, 3).foreach(i => i must be_>(0)) }
 */
trait ExpectationsBlock { this: FragmentsBuilder =>
  /**
   * add a new example using 'in' but ending with a Unit block
   */
  implicit override def inExample(s: String) = new InExampleUnit(s)
  class InExampleUnit(s: String) extends InExample(s) {
    def in(block: =>Unit): Example = exampleFactory.newExample(s, {block; success})
    def >>(block: =>Unit): Example = in(block)
  }
}
/**
 * allow to write "this block has several examples" in { Seq(1, 2, 3).foreach(i => "example"+i >> ok) }
 */
trait ExamplesBlock { this: FragmentsBuilder =>
  /**
   * add a new example using 'in' but ending with a Unit block
   */
  implicit override def inExample(s: String) = new InExampleUnit(s)
  class InExampleUnit(s: String) extends InExample(s) {
    def in(block: =>Unit): Fragment = {
      new InExample(s) >> {
        addFragments(FF.br)
        new NameSpace { block }
      }
      StandardFragments.Tab(0)
    }
    def >>(block: =>Unit): Fragment = in(block)
  }
}
/**
 * This trait can be used to deactivate implicits for building fragments
 */
trait NoFragmentsBuilder extends FragmentsBuilder {
  override def described(s: String): Described = super.described(s)
  override def inExample(s: String): InExample = super.inExample(s)
  override def title(s: String)                = super.title(s)
  override def text(s: String)                 = super.text(s)
}

import scalaz.{TreeLoc, Scalaz, Tree}
import Scalaz._
import Tree._
import data.Trees._
trait SideEffectingCreationPaths extends SpecificationNavigation {

  /**
  * This tree loc contains the "path" of Examples and Actions when they are created in a block creating another fragment
  * For example:
  *
  * "this" should {
  *   "create an example" >> ok
  * }
  *
  * The execution of the block above creates a Text fragment followed by an example. The blocksTree tree tracks the order of creation
  * so that we can attach a "block creation path" to the Example showing which fragment creation precedes him. This knowledge is used to
  * run a specification in the "isolation" mode were the changes in local variables belonging to blocks are not seen by
  * other examples
  */
  private[mutable] var blocksTree: TreeLoc[(Int, Any)] = leaf((0, Text("root"): Any)).loc

  /** when a target path is specified we might limit the creation of fragments to only the fragments on the desired path */
  private[mutable] var targetPath: Option[CreationPath] = None

  /** list of actions to create fragments */
  private[mutable] var effects: scala.collection.mutable.ListBuffer[() => Unit] = new scala.collection.mutable.ListBuffer()

  /**
   * play all the effects. After each executed effect, new effects might have been created.
   * Push them first at the beginning of the effects list so that they can be played first
   */
  private[mutable] def replay() = {
    def targetReached = targetPath.map(_ == creationPath).getOrElse(false)

    while (!effects.isEmpty) {
      val effect = effects.remove(0)
      val rest = effects.take(effects.size)
      effects = new scala.collection.mutable.ListBuffer()
      effect.apply()
      effects.append(rest:_*)
      if (targetReached)
        effects = effects.take(1)
    }
  }

  /** @return the Tree of creation paths */
  private[mutable] def blocksCreationTree = blocksTree.toTree.map(_._1)

  /** @return the current path to root */
  private[mutable] def creationPath = MutableCreationPath(blocksTree.lastChild.getOrElse(blocksTree).map(_._1).path.reverse.toIndexedSeq)

  private def nextNodeNumber = blocksTree.lastChild.map(_.getLabel._1 + 1).getOrElse(0)

  private[mutable] def startBlock() {
    effect(blocksTree = blocksTree.lastChild.getOrElse(blocksTree))
  }

  private[mutable] def endBlock() {
    effect(blocksTree = blocksTree.getParent)
  }

  private[mutable] def executeBlock[T](block: =>T) = {
    startBlock()
    effect {
      targetPath match {
        case Some(path) => if (path.startsWith(creationPath)) block
        case None       => block
      }
    }
    endBlock()
  }

  private[mutable] def effect(a: =>Unit) {
    effects.append(() => a)
  }

  private[mutable] def addBlockElement(e: Any) {
    effect(blocksTree = blocksTree.addChild((nextNodeNumber, e)))
  }

  /**
  * @return the list of fragments which have been created before a given one
  */
  private[specs2]
  override def fragmentsTo(f: Fragment): Seq[Fragment] = {
    // set the target path
    targetPath = f match {
      case e: Example => e.creationPath
      case a: Action  => a.creationPath
      case other      => None
    }
    // return the fragments created till all path nodes have been created
    content.fragments
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy