Skip to content

Commit

Permalink
Added TestRenderer.act support
Browse files Browse the repository at this point in the history
  • Loading branch information
viktor-podzigun committed Feb 15, 2019
1 parent 0fd0145 commit 3a68390
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 79 deletions.
6 changes: 5 additions & 1 deletion core/src/main/scala/scommons/react/FunctionComponent.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ trait FunctionComponent[T] extends UiComponent[T] {

type Props = reactjs.React.Props[T]

protected def name: String = getClass.getSimpleName
protected def name: String = {
val name = getClass.getName
name.drop(name.lastIndexOf('.') + 1)
.stripSuffix("$")
}

protected def render(props: Props): ReactElement

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ object FunctionComponentDemo extends FunctionComponent[FunctionComponentDemoProp
protected def render(props: Props): ReactElement = {
val data = props.wrapped

<.div()(
<.div(^.className := "root")(
data.values.zipWithIndex.map { case (value, index) =>
<.div(^.key := s"$index")(value)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ package scommons.react.showcase

import scommons.react._
import scommons.react.test.TestSpec
import scommons.react.test.util.ShallowRendererUtils
import scommons.react.test.dom.util.TestDOMUtils
import scommons.react.test.util.{ShallowRendererUtils, TestRendererUtils}

import scala.scalajs.js

class FunctionComponentDemoSpec extends TestSpec with ShallowRendererUtils {
class FunctionComponentDemoSpec extends TestSpec
with TestDOMUtils
with ShallowRendererUtils
with TestRendererUtils {

it should "set function name" in {
//given
Expand All @@ -22,6 +26,27 @@ class FunctionComponentDemoSpec extends TestSpec with ShallowRendererUtils {
}
}

it should "render component in dom" in {
//given
val props = FunctionComponentDemoProps(List("test"))
val comp = <(FunctionComponentDemoSpec.Wrapper())(^.wrapped := props)(
"some child"
)

//when
domRender(comp)

//then
assertDOMElement(domContainer.querySelector(".root"),
<.div(^("class") := "root")(
props.values.map { v =>
<.div()(v)
},
"some child"
)
)
}

it should "shallow render as top component" in {
//given
val props = FunctionComponentDemoProps(List("test"))
Expand All @@ -34,7 +59,7 @@ class FunctionComponentDemoSpec extends TestSpec with ShallowRendererUtils {

//then
assertNativeComponent(result,
<.div()(
<.div(^.className := "root")(
props.values.zipWithIndex.map { case (v, i) =>
<.div(^.key := s"$i")(v)
},
Expand Down Expand Up @@ -74,6 +99,53 @@ class FunctionComponentDemoSpec extends TestSpec with ShallowRendererUtils {
child shouldBe "some child"
})
}

it should "test render component" in {
//given
val props = FunctionComponentDemoProps(List("test"))
val comp = <(FunctionComponentDemoSpec.Wrapper())(^.wrapped := props)(
"some child"
)

//when
val result = testRender(comp)

//then
assertTestComponent(result, FunctionComponentDemo)({ resProps =>
resProps shouldBe props
}, { case List(child) =>
assertNativeComponent(child,
<.div(^.className := "root")(
props.values.map { v =>
<.div()(v)
},
"some child"
)
)
})
}

it should "re-render component even if props hasn't changed" in {
//given
var isRendered = false
val compClass = new FunctionComponent[FunctionComponentDemoProps] {
protected def render(props: Props): ReactElement = {
isRendered = true
<.div.empty
}
}
val props = FunctionComponentDemoProps(List("test"))
val renderer = createRenderer()
renderer.render(<(compClass())(^.wrapped := props)())
isRendered shouldBe true
isRendered = false

//when
renderer.render(<(compClass())(^.wrapped := props)())

//then
isRendered shouldBe true
}
}

object FunctionComponentDemoSpec {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
package scommons.react.showcase.hooks

import scommons.react.test.TestSpec
import scommons.react.test.dom.TestDomSpec
import scommons.react.test.dom.raw.ReactTestUtils.Simulate
import scommons.react.test.util.ShallowRendererUtils
import scommons.react.test.dom.util.TestDOMUtils
import scommons.react.test.util.{ShallowRendererUtils, TestRendererUtils}

class UseStateDemoSpec extends TestSpec
with TestDomSpec
with ShallowRendererUtils {
with TestDOMUtils
with ShallowRendererUtils
with TestRendererUtils {

it should "increase counters when onClick" in {
//given
render(<(UseStateDemo())()())
domRender(<(UseStateDemo())()())

val p = domContainer.querySelector("p")
p.textContent shouldBe "counter1: 0, counter2: 0"
val button1 = domContainer.querySelector(".counter1")
val button2 = domContainer.querySelector(".counter2")

//when & then
fireEvent(Simulate.click(button1))
fireDomEvent(Simulate.click(button1))
domContainer.querySelector("p").textContent shouldBe "counter1: 1, counter2: 0"

//when & then
fireEvent(Simulate.click(button2))
fireEvent(Simulate.click(button1))
fireDomEvent(Simulate.click(button2))
fireDomEvent(Simulate.click(button1))
domContainer.querySelector("p").textContent shouldBe "counter1: 2, counter2: 1"
}

Expand All @@ -44,4 +45,21 @@ class UseStateDemoSpec extends TestSpec
)
)
}

it should "test render component" in {
//given
val comp = <(UseStateDemo())()()

//when
val result = testRender(comp)

//then
assertNativeComponent(result,
<.div()(
<.p()("counter1: 0, counter2: 0"),
<.button(^.className := "counter1")("Increase counter1"),
<.button(^.className := "counter2")("Increase counter2")
)
)
}
}
34 changes: 0 additions & 34 deletions test-dom/src/main/scala/scommons/react/test/dom/TestDomSpec.scala

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,17 +1,54 @@
package scommons.react.test.dom.util

import io.github.shogowada.scalajs.reactjs.ReactDOM
import io.github.shogowada.scalajs.reactjs.elements.ReactElement
import io.github.shogowada.statictags
import org.scalajs.dom
import org.scalajs.dom._
import org.scalatest.Matchers
import org.scalatest.{BeforeAndAfterEach, Matchers, Suite}
import scommons.react.UiComponent
import scommons.react.test.dom.raw.ReactTestUtils._
import scommons.react.test.dom.raw.{ReactTestUtils, TestReactDOM}

import scala.scalajs.js

trait TestDOMUtils extends Matchers {
trait TestDOMUtils extends Suite with Matchers with BeforeAndAfterEach {

protected var domContainer: Element = _

override protected def beforeEach(): Unit = {
super.beforeEach()

domContainer = document.createElement("div")
document.body.appendChild(domContainer)
}

override protected def afterEach(): Unit = {
super.afterEach()

document.body.removeChild(domContainer)
domContainer = null
}

def domRender(element: ReactElement): Unit = {
ReactTestUtils.act { () =>
ReactDOM.render(element, domContainer)
}
}

def fireDomEvent(block: => Unit): Unit = {
ReactTestUtils.act { () =>
block
}
}

def createDomEvent[T <: Event](args: js.Any*)(implicit tag: js.ConstructorTag[T]): T = {
js.Dynamic.newInstance(tag.constructor)(args: _*).asInstanceOf[T]
}

//////////////////////////////////////////////////////////////////////////////
//START of deprecated methods section:
// use domRender(...) and assert domContainer

def renderIntoDocument(element: ReactElement): Instance = ReactTestUtils.renderIntoDocument(element)

Expand All @@ -21,12 +58,12 @@ trait TestDOMUtils extends Matchers {

private def getComponentProps[T](component: Instance): T = component.props.wrapped.asInstanceOf[T]

def createDomEvent[T <: Event](args: js.Any*)(implicit tag: js.ConstructorTag[T]): T = {
js.Dynamic.newInstance(tag.constructor)(args: _*).asInstanceOf[T]
}

def findReactElement(component: js.Any): dom.Element = asElement(TestReactDOM.findDOMNode(component))

//END of deprecated methods section
//////////////////////////////////////////////////////////////////////////////


private def asElement(node: Node): dom.Element = node.asInstanceOf[dom.Element]

def assertDOMElement(result: dom.Element, expected: statictags.Element): Unit = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ import scala.scalajs.js.annotation.JSImport
@js.native
object TestRenderer extends js.Object {

/**
* To prepare a component for assertions, wrap the code rendering it and performing updates inside an act() call.
* This makes your test run closer to how React works in the browser.
*/
def act(block: js.Function0[Unit]): Unit = js.native

def create(element: ReactElement): TestRenderer = js.native
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,26 @@ trait TestRendererUtils extends Matchers {
} shouldBe Nil
}

def createRenderer(element: ReactElement): TestRenderer = TestRenderer.create(element)
def createTestRenderer(element: ReactElement): TestRenderer = {
var result: TestRenderer = null
TestRenderer.act { () =>
result = TestRenderer.create(element)
}

result
}

def render(element: ReactElement): TestInstance = {
val root = createRenderer(element).root
def testRender(element: ReactElement): TestInstance = {
val root = createTestRenderer(element).root
root.children(0)
}

def testUpdate(renderer: TestRenderer, element: ReactElement): Unit = {
TestRenderer.act { () =>
renderer.update(element)
}
}

def findComponentProps[T](renderedComp: TestInstance, searchComp: UiComponent[T]): T = {
findProps[T](renderedComp, searchComp).headOption match {
case Some(comp) => comp
Expand Down Expand Up @@ -59,19 +72,23 @@ trait TestRendererUtils extends Matchers {
result.toList
}

def assertComponent[T](result: TestInstance, expectedComp: UiComponent[T])
(assertProps: T => Assertion,
assertChildren: List[TestInstance] => Assertion = _ => Succeeded): Assertion = {
def assertTestComponent[T](result: TestInstance, expectedComp: UiComponent[T])
(assertProps: T => Assertion,
assertChildren: List[TestInstance] => Assertion = _ => Succeeded): Assertion = {

result.`type` shouldBe expectedComp.apply()

assertProps(result.props.wrapped.asInstanceOf[T])
assertChildren(getComponentChildren(result))
}

def assertNativeComponent(result: TestInstance, expectedElement: Element): Assertion = {
assertNativeComponent(result, expectedElement, expectNoChildren)
}

def assertNativeComponent(result: TestInstance,
expectedElement: Element,
assertChildren: List[TestInstance] => Assertion = expectNoChildren): Assertion = {
assertChildren: List[TestInstance] => Assertion): Assertion = {

result.`type` shouldBe expectedElement.name

Expand Down

0 comments on commit 3a68390

Please sign in to comment.