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

japgolly.scalajs.react.test.ReactTestUtils2.scala Maven / Gradle / Ivy

There is a newer version: 3.0.0-beta8
Show newest version
package japgolly.scalajs.react.test

import japgolly.scalajs.react._
import japgolly.scalajs.react.hooks.Hooks
import japgolly.scalajs.react.internal.CoreGeneral._
import japgolly.scalajs.react.test.internal.WithDsl
import japgolly.scalajs.react.util.DefaultEffects.{Sync => DS}
import japgolly.scalajs.react.util.Effect._
import japgolly.scalajs.react.util.{ImplicitUnit, JsUtil}
import org.scalajs.dom.html.Element
import org.scalajs.dom.{console, document}

object ReactTestUtils2 extends ReactTestUtils2 {

  val raw = japgolly.scalajs.react.test.facade.ReactTestUtils

  IsReactActEnvironment = true

  private[ReactTestUtils2] object Internals {
    val reactDataAttrRegex = """\s+data-react\S*?\s*?=\s*?".*?"""".r
    val reactTextCommentRegex = """""".r

    def warnOnError(prefix: String)(a: => Any): Unit =
      try {
        a
        ()
      } catch {
        case t: Throwable =>
          console.warn(s"$prefix: $t")
      }
  } // Internals
}

trait ReactTestUtils2 extends japgolly.scalajs.react.test.internal.ReactTestUtilExtensions {
  import ReactTestUtils2._
  import ReactTestUtils2.Internals._

  type Unmounted = GenericComponent.Unmounted[_, Unit]

  type CompType = GenericComponent.ComponentRaw {type Raw <: japgolly.scalajs.react.facade.React.ComponentClassUntyped }

  // ===================================================================================================================

  def IsReactActEnvironment(): Boolean =
    JsUtil.global().IS_REACT_ACT_ENVIRONMENT.asInstanceOf[Boolean]

  def IsReactActEnvironment_=(b: Boolean): Unit =
    JsUtil.global().IS_REACT_ACT_ENVIRONMENT = b

  /** When writing UI tests, tasks like rendering, user events, or data fetching can be considered as "units" of
    * interaction with a user interface. React provides a helper called act() that makes sure all updates related to
    * these "units" have been processed and applied to the DOM before you make any assertions:
    *
    * {{{
    *   act {
    *     // render components
    *   }
    *   // make assertions
    * }}}
    *
    * This helps make your tests run closer to what real users would experience when using your application.
    */
  def act[A](body: => A): A = {
    var a = Option.empty[A]
    raw.act(() => { a = Some(body) })
    a.getOrElse(throw new RuntimeException("React's TestUtils.act didn't seem to complete."))
  }

  /** When writing UI tests, tasks like rendering, user events, or data fetching can be considered as "units" of
    * interaction with a user interface. React provides a helper called act() that makes sure all updates related to
    * these "units" have been processed and applied to the DOM before you make any assertions:
    *
    * {{{
    *   await act(async () => {
    *     // render components
    *   });
    *   // make assertions
    * }}}
    *
    * This helps make your tests run closer to what real users would experience when using your application.
    */
  def actAsync[F[_], A](body: F[A])(implicit F: Async[F]): F[A] = {
    F.flatMap(F.delay(new Hooks.Var(Option.empty[A]))) { ref =>
      def setAsync(a: A): F[Unit] = F.delay(DS.runSync(ref.set(Some(a))))
      val body2 = F.flatMap(body)(setAsync)
      val body3 = F.fromJsPromise(raw.actAsync(F.toJsPromise(body2)))
      F.map(body3)(_ => ref.value.getOrElse(throw new RuntimeException("React's TestUtils.act didn't seem to complete.")))
    }
  }

  def newElement(): Element = {
    val cont = document.createElement("div").domAsHtml
    document.body.appendChild(cont)
    cont
  }

  def removeElement(e: Element): Unit =
    warnOnError("Failed to remove element: " + e) {
      document.body.removeChild(e)
    }

  /** Turn `<div data-reactroot="">hello</div>`
    * into `<div>hello</div>`
    */
  def removeReactInternals(html: String): String = {
    var h = html
    h = reactTextCommentRegex.replaceAllIn(h, "")
    h = reactDataAttrRegex.replaceAllIn(h, "")
    h
  }

  val withElement: WithDsl[Element, ImplicitUnit] =
    WithDsl(newElement())(removeElement)

  val withReactRoot: WithDsl[TestReactRoot, ImplicitUnit] =
    withElement.mapResourse(TestReactRoot(_))(_.unmount())

  def withRendered[A](unmounted: A): WithDsl[TestDomWithRoot, Renderable[A]] =
    WithDsl[TestDomWithRoot, Renderable[A]] { (renderable, cleanup) =>
      val root = withReactRoot.setup(implicitly, cleanup)
      act(root.render(unmounted)(renderable))
      root.selectFirstChild()
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy