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

teststate.domzipper.DomZipper.scala Maven / Gradle / Ivy

The newest version!
package teststate.domzipper

import teststate.domzipper.DomZipper._
import teststate.domzipper.ErrorHandler.ErrorHandlerResultOps

trait DomZipper[F[_], Dom, A, Self[G[_], B] <: DomZipper[G, Dom, B, Self]] {

  // ====================
  // Self & configuration
  // ====================

  protected def self: Self[F, A]

  protected implicit def F: ErrorHandler[F]

  protected[domzipper] def htmlScrub: HtmlScrub

  def scrubHtml(f: HtmlScrub): Self[F, A]

  final def scrubHtml(f: String => String): Self[F, A] =
    scrubHtml(HtmlScrub(f))

  def isCapable(c: DomZipper.Capability): Boolean

  def describe: String

  @deprecated("Use .describe", "2.3.0")
  final def describeLoc: String = describe

  def enrichErr(msg: String): String

  // =========
  // Utilities
  // =========

  /** To ensure that DOM doesn't change in the middle of an observation, use this pattern:
    *
    * {{{
    *   class Obs($: DomZipper) {
    *
    *     // Before making any observations...
    *     private val checkConsistency = $.startConsistencyCheck()
    *
    *     // ... obs here ...
    *
    *     // After making all observations...
    *     checkConsistency()
    *   }
    * }}}
    *
    * (This assumes you're using ErrorHandler.Throw)
    */
  def startConsistencyCheck(): () => F[Unit] = {
    val initial = outerHTML
    () => {
      val current = outerHTML
      if (initial == current)
        F.pass(())
      else
        F.fail(
          s"""
            |Inconsistency detected.
            |
            |Initial HTML:
            |$initial
            |
            |Current HTML:
            |$current
          """.stripMargin.trim)
    }
  }

  /** To ensure that DOM doesn't change in the middle of an observation, replace code like...
    *
    * {{{
    *   new Obs($)
    * }}}
    *
    * ...with code like...
    *
    * {{{
    *   $.ensureConsistency(new Obs(_))
    * }}}
    */
  final def ensureConsistency[B](f: this.type => B): F[B] = {
    val checkConsistency = startConsistencyCheck()
    for {
      b <- F.attempt(f(this))
      _ <- checkConsistency()
    } yield b
  }

  final def prepare[B](f: Self[F, A] => B): () => B =
    () => f(self)

  // =================
  // Comonad and focus
  // =================

  def extract: A

  def map[B](f: A => B): Self[F, B]

  def extend[B](f: Self[F, A] => B): Self[F, B]

  def duplicate: Self[F, Self[F, A]]

  def unmap:  Self[F, Dom]

  // =======
  // Descent
  // =======

  def apply(name: String, sel: String, which: MofN): F[Self[F, A]]
  def child(name: String, sel: String, which: MofN): F[Self[F, A]]

  final def apply(sel: String)              : F[Self[F, A]] = apply("", sel)
  final def apply(sel: String, which: MofN) : F[Self[F, A]] = apply("", sel, which)
  final def apply(name: String, sel: String): F[Self[F, A]] = apply(name, sel, MofN.Sole)

  final def child(sel: String)              : F[Self[F, A]] = child("", sel)
  final def child(which: MofN = MofN.Sole)  : F[Self[F, A]] = child("", which)
  final def child(sel: String, which: MofN) : F[Self[F, A]] = child("", sel, which)
  final def child(name: String, sel: String): F[Self[F, A]] = child(name, sel, MofN.Sole)

  // ====================
  // DOM & DOM inspection
  // ====================

  def parent: F[Self[F, A]]

  def dom: Dom

  protected def _outerHTML: String
  protected def _innerHTML: String

  def matches(css: String): F[Boolean]

  def getAttribute(name: String): Option[String]

  def tagName: String

  def innerText: String

  def checked: F[Boolean]

  def classes: Set[String]

  def value: F[String]

  final def needAttribute(name: String): F[String] =
    F.option(getAttribute(name), s"$tagName doesn't have attribute $name")

  final def outerHTML: String = htmlScrub run _outerHTML
  final def innerHTML: String = htmlScrub run _innerHTML

  def collect01(sel: String): DomCollection[Self, F, Option, Dom, A]
  def collect0n(sel: String): DomCollection[Self, F, Vector, Dom, A]
  def collect1n(sel: String): DomCollection[Self, F, Vector, Dom, A]

  def children01: DomCollection[Self, F, Option, Dom, A]
  def children0n: DomCollection[Self, F, Vector, Dom, A]
  def children1n: DomCollection[Self, F, Vector, Dom, A]

  def children01(sel: String): DomCollection[Self, F, Option, Dom, A]
  def children0n(sel: String): DomCollection[Self, F, Vector, Dom, A]
  def children1n(sel: String): DomCollection[Self, F, Vector, Dom, A]

  final def editables01 = collect01(EditableSel)
  final def editables0n = collect0n(EditableSel)
  final def editables1n = collect1n(EditableSel)

  def count(sel: String): Int =
    collect0n(sel).size

  def exists(sel: String): Boolean =
    collect0n(sel).nonEmpty

  def exists(sel: String, suchThat: Self[F, A] => Boolean): Boolean =
    collect0n(sel).filter(suchThat).nonEmpty

  def findSelfOrChildWithAttribute(attr: String): F[Option[Self[F, A]]] =
    getAttribute(attr) match {
      case None    => collect01(s"*[$attr]").zippers
      case Some(_) => F pass Some(self)
    }

  /** The currently selected option in a <select> dropdown. */
  def selectedOption: F[DomCollection[Self, F, Option, Dom, A]] =
    tagName.toUpperCase match {
      case "SELECT" => F pass collect01("option[selected]")
      case x        => F fail enrichErr(s"<$x> is not a