Skip to content

Commit 88459d5

Browse files
committed
Added Sel to test module
1 parent 01fafe2 commit 88459d5

File tree

4 files changed

+164
-0
lines changed

4 files changed

+164
-0
lines changed

HISTORY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ History
2727
* Added `ScalazReact.ReactS.liftR` of type `(S ⇒ ReactST[M,S,A]) ⇒ ReactST[M,S,A]`.
2828
* `ScalazReact`'s `~~>` and `runState` functions are now lazy.
2929
* Upgrade [Scalatags](https://github.com/lihaoyi/scalatags) to 0.4.2.
30+
* Added `Sel` to easily lookup DOM in your tests.
31+
Eg. `Sel("div.inner a.active.blue span") find c`
3032

3133
### 0.4.1 ([commit log](https://github.com/japgolly/scalajs-react/compare/v0.4.0...v0.4.1))
3234

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,15 @@ val s = Simulation.focusChangeBlur("hi")
138138
s run component
139139
```
140140

141+
142+
DOM lookup is much easier than using `ReactTestUtils` directly by instead using `Sel`.
143+
`Sel` allows you to use a jQuery/CSS-like selector to lookup a DOM element or subset.
144+
Example:
145+
```scala
146+
val dom = Sel(".inner a.active.new") find myComponent
147+
```
148+
149+
141150
Also included is [DebugJs](https://github.com/japgolly/scalajs-react/blob/master/test/src/main/scala/japgolly/scalajs/react/test/DebugJs.scala), a dumping ground for functionality useful when testing JS. `inspectObject` can be tremendously useful.
142151

143152
#### SBT
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package japgolly.scalajs.react.test
2+
3+
import scala.scalajs.js.Array
4+
import ReactTestUtils._
5+
6+
/**
7+
* Path to a subset of DOM.
8+
* Much easier and more powerful than what you find in ReactTestUtils.
9+
*
10+
* Example: Sel("div.inner a.active.blue span") find c
11+
*/
12+
sealed abstract class Sel {
13+
import Sel._
14+
15+
final def &(s: Sel): Sel =
16+
this match {
17+
case Tag(_) | Cls(_) => And(this, s :: Nil)
18+
case And(h, t) => And(s, h :: t)
19+
case Descent(p, c) => Descent(p, c & s)
20+
case E => s
21+
case =>
22+
}
23+
24+
final def >>(s: Sel): Sel = Descent(this, s)
25+
26+
final def findAll(i: ComponentM): Array[ComponentM] = this match {
27+
case Tag(n) => scryRenderedDOMComponentsWithTag(i, n)
28+
case Cls(n) => scryRenderedDOMComponentsWithClass(i, n)
29+
case And(h, t) => (h.findAll(i) /: t)((q, s) => q intersect s.findAll(i))
30+
case Descent(p, c) => val r = p findAll i; r flatMap c.findAll filterNot (r contains _)
31+
case E => Array(i)
32+
case => Array()
33+
}
34+
35+
final def findE(i: ComponentM): Either[String, ComponentM] = {
36+
val a = findAll(i)
37+
a.length match {
38+
case 1 => Right(a(0))
39+
case 0 => Left(s"DOM not found for [$this]")
40+
case n => Left(s"Too many DOM elements ($n) found for [$this]")
41+
}
42+
}
43+
44+
final def findO(i: ComponentM): Option[ComponentM] =
45+
findE(i).right.toOption
46+
47+
final def find(i: ComponentM): ComponentM =
48+
findE(i).fold(sys.error, identity)
49+
}
50+
51+
object Sel {
52+
@inline final def id: Sel = E
53+
54+
case object E extends Sel {
55+
override def toString = "*"
56+
}
57+
58+
case object extends Sel {
59+
override def toString = ""
60+
}
61+
62+
final case class Tag(tag: String) extends Sel {
63+
override def toString = tag
64+
}
65+
66+
final case class Cls(cls: String) extends Sel {
67+
override def toString = "." + cls
68+
}
69+
70+
final case class And(h: Sel, t: List[Sel]) extends Sel {
71+
override def toString = (h :: t).mkString("(", " & ", ")")
72+
}
73+
74+
final case class Descent(p: Sel, c: Sel) extends Sel {
75+
override def toString = p + " >> " + c
76+
}
77+
78+
def apply(css: String): Sel = {
79+
def lvl(s: String): Sel =
80+
(id /: s.split("(?=\\.)").filter(_.nonEmpty))((q,a) => q & elem(a))
81+
// .filter here ↗ due to https://github.com/scala-js/scala-js/issues/1171
82+
def elem(s: String): Sel =
83+
if (s.isEmpty)
84+
else if (s == "*") id
85+
else if (s.head == '.') Cls(s.tail)
86+
else Tag(s)
87+
css.split("\\s+").filter(_.nonEmpty).toList match {
88+
case Nil =>
89+
case l1 :: ln => (lvl(l1) /: ln)((q, l) => q >> lvl(l))
90+
}
91+
}
92+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package japgolly.scalajs.react.test
2+
3+
import utest._
4+
import japgolly.scalajs.react._, vdom.ReactVDom._, all._
5+
6+
object SelTest extends TestSuite {
7+
8+
lazy val C = ReactComponentB[Unit]("C").render(_ =>
9+
div(
10+
h1(span("Monuments"), "2014"),
11+
p(span("The Amanuensis")),
12+
h3("Quasimodo"),
13+
h4(cls := "monuments t3", "Atlas"),
14+
div(cls := "mastodon t3", "High road"),
15+
div(cls := "mastodon t5", "Chimes at midnight")
16+
)
17+
).buildU
18+
19+
val tests = TestSuite {
20+
val c = ReactTestUtils renderIntoDocument C()
21+
22+
def test1(s: String, e: String) = {
23+
val a = Sel(s).find(c).getDOMNode().innerHTML
24+
assert(a == e)
25+
}
26+
27+
def testF(errFrag: String) = (s: String) => {
28+
val a: Either[String, String] = Sel(s).findE(c).right.map(_.getDOMNode().innerHTML)
29+
if (a.isRight) println(Sel(s))
30+
assert(a.isLeft, a.toString contains errFrag)
31+
}
32+
def testNonUnique = testF("Too many")
33+
def testNotFound = testF("not found")
34+
35+
'tag - test1("h3", "Quasimodo")
36+
37+
'cls - test1(".t5", "Chimes at midnight")
38+
39+
'cls2 {
40+
test1(".t3.mastodon", "High road")
41+
test1(".mastodon.t3", "High road")
42+
}
43+
44+
'tagAndCls {
45+
test1("h4.t3", "Atlas")
46+
testNotFound("h4.t0")
47+
test1("div.t3", "High road")
48+
test1("div.mastodon.t3", "High road")
49+
}
50+
51+
'decent {
52+
test1("h1 span", "Monuments")
53+
test1("p span", "The Amanuensis")
54+
test1("div p span", "The Amanuensis")
55+
testNonUnique("div span")
56+
testNotFound("p h3")
57+
testNotFound("h3 div")
58+
testNotFound(".t3 .mastodon")
59+
}
60+
}
61+
}

0 commit comments

Comments
 (0)