- Concept
- Walkthrough
- Sample Output
In this example, there is a scalajs-react
component called TodoComponent
.
It renders a view like this:
[_________________] [Add]
Todo List
=========
* Do washing. [Complete]
* Buy milk.
* Buy bread. [Complete]
* Make toast. [Complete]
[X] Show completed.
Complete: 1
Pending : 3
Total : 4
The idea is that:
- at the top, you enter a task and click
Add
, which sends it to the Todo List. - Tasks in the Todo List have a
Complete
button beside them which you use to make them as done. - Completed tasks can be hidden using the checkbox.
- The summary at the bottom is always up-to-date.
Here we want to use TestState itself (core
), the scalajs-react extensions, and the DomZipper.
We don't want to have to think about how DomZipper will query DOM so we will use the default dom-zipper-sizzle
bundle which takes care of it.
Firstly we create a TestState
object for the project that combines our dependencies.
See MyTestState.scala.
Next we create a class to observe the component.
class TodoObs($: HtmlDomZipper)
And we create a DSL and put it somewhere. Let's also keep track of what we expect the number of completed and total items will be.
object TodoTestDsl {
case class State(total: Int, completed: Int)
val dsl = Dsl[Unit, TodoObs, State]
}
Now we're set to start writing tests.
Let's start with an invariant: Total
should always equal Complete
+ Pending
.
Firstly, let's capture the numbers on the screen (i.e. the component's output). There are a number of ways to do this, an easy way is by CSS selector expressions.
We know there's only one <table>
in the output, and that the numbers are in the 2nd <td>
.
And so we write this into our observation.
class TodoObs($: HtmlDomZipper) {
// There is only one <table>
private val summaryTable = $("table")
// Find the row first, then get its 2nd <td>
private def summaryInt(name: String): Int =
summaryTable(s"tr:contains('$name')")("td", 2 of 2).innerText.toInt
val total = summaryInt("Total")
val complete = summaryInt("Complete")
val pending = summaryInt("Pending")
}
And then we write the invariant:
dsl.focus("Summary total").value(_.obs.total)
.assert.equalBy(i => i.obs.complete + i.obs.pending)
Now let's ensure that the observed total always matches our expected total in our State
class.
dsl.focus("Total items").obsAndState(_.total, _.total).assert.equal
// TodoObs ↗ ↖ State
Let's write an action to toggle the Show completed
checkbox.
First we capture the checkbox DOM,
import org.scalajs.dom.html
class TodoObs($: HtmlDomZipper) {
val showCompleteInput =
$("label:contains('Show') input").domAs[html.Input]
// ...
}
Then we create an action around it.
import japgolly.scalajs.react.test.ReactTestUtils.Simulate
val toggleShowCompleted =
dsl.action("Toggle ShowCompleted")(Simulate change _.obs.showCompleteInput)
Now let's make sure that whenever it's invoked, it ensures that the checkbox changes. First collect the value:
class TodoObs($: HtmlDomZipper) {
val showCompleteInput =
$("label:contains('Show') input").domAs[html.Input]
val showingComplete: Boolean =
showCompleteInput.checked
// ...
}
then update our test dsl.
val showingComplete = dsl.focus("Showing Complete").value(_.obs.showingComplete)
val toggleShowCompleted =
dsl.action("Toggle ShowCompleted")(Simulate change _.obs.showCompleteInput) +>
showingComplete.assert.change
As for how to glue everything together and run tests, it's best to just have a look at the source yourself. It's short.
You can also clone this repo and run the tests via:
sbt exampleReactJS/test
Here is the code for the test in this example.
addItem("hello")
>> addItem("hello2")
>> addItem(" blah 3 ") +> visibleItemNames.assert.contains("blah 3")
>> completeItem("hello2") +> visibleItemNames.size.assert.decrement
>> toggleShowCompleted +> visibleItemNames.size.assert.increment
>> toggleShowCompleted +> visibleItemNames.assert("hello", "blah 3")
This is what you see (by default) when the test passes.
This is an example of test failure.
You can also tell TestState to show you everything it does in glorious and excruciating detail, if you so wish.