Skip to content

Commit 42c49d6

Browse files
authored
Merge pull request #1000 from japgolly/topic/warnings
Support turning React warnings into errors
2 parents ce2e082 + 0f58a02 commit 42c49d6

File tree

17 files changed

+516
-49
lines changed

17 files changed

+516
-49
lines changed

build.sbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ val ghpagesMacros = ScalaJsReact.ghpagesMacros
2323
val root = ScalaJsReact.root
2424
val scalafixRules = ScalaJsReact.scalafixRules
2525
val tests = ScalaJsReact.tests
26+
val testUtilMacros = ScalaJsReact.testUtilMacros
2627
val testUtil = ScalaJsReact.testUtil
2728
val util = ScalaJsReact.util
2829
val utilCatsEffect = ScalaJsReact.utilCatsEffect
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package japgolly.scalajs.react.internal
2+
3+
import japgolly.microlibs.compiletime._
4+
import scala.reflect.macros.blackbox.Context
5+
6+
abstract class ConfigMacros(val c: Context) extends MacroUtils {
7+
import c.universe._
8+
9+
protected def readConfig(key: String): Option[String] =
10+
(Option(System.getProperty(key, null)) orElse Option(System.getenv(key)))
11+
.map(_.trim.toLowerCase)
12+
.filter(_.nonEmpty)
13+
14+
protected def modStr(expr: c.Expr[String])(f: String => c.Expr[String]): c.Expr[String] =
15+
expr match {
16+
case Expr(Literal(Constant(s: String))) => f(s)
17+
case _ => expr
18+
}
19+
20+
protected implicit def lit(s: String): c.Expr[String] =
21+
c.Expr(Literal(Constant(s)))
22+
}

coreGeneric/src/main/scala-2/japgolly/scalajs/react/internal/ScalaJsReactConfigMacros.scala

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,16 @@
11
package japgolly.scalajs.react.internal
22

3-
import japgolly.microlibs.compiletime._
43
import scala.reflect.macros.blackbox.Context
54

65
object ScalaJsReactConfigMacros {
76
final val KeyCompNameAll = "japgolly.scalajs.react.component.names.all"
87
final val KeyCompNameAuto = "japgolly.scalajs.react.component.names.implicit"
98
}
109

11-
class ScalaJsReactConfigMacros(val c: Context) extends MacroUtils {
10+
class ScalaJsReactConfigMacros(override val c: Context) extends ConfigMacros(c) {
1211
import ScalaJsReactConfigMacros._
1312
import c.universe._
1413

15-
private def readConfig(key: String): Option[String] =
16-
(Option(System.getProperty(key, null)) orElse Option(System.getenv(key)))
17-
.map(_.trim.toLowerCase)
18-
.filter(_.nonEmpty)
19-
20-
private def modStr(expr: c.Expr[String])(f: String => c.Expr[String]): c.Expr[String] =
21-
expr match {
22-
case Expr(Literal(Constant(s: String))) => f(s)
23-
case _ =>
24-
// warn(s"Unable to transform component name:\n $expr\n ${showRaw(expr)}")
25-
expr
26-
}
27-
28-
private implicit def lit(s: String): c.Expr[String] =
29-
c.Expr(Literal(Constant(s)))
30-
3114
def automaticComponentName(displayName: c.Expr[String]): c.Expr[String] =
3215
modStr(displayName)(_automaticComponentName(_))
3316

doc/CONFIG.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* [`.component.names.all`](#componentnamesall)
77
* [`.component.names.implicit`](#componentnamesimplicit)
88
* [`.config.class` *(Scala 3 only)*](#configclass-scala-3-only)
9+
* [`.test.warnings.react`](#testwarningsreact)
910
* Runtime Settings *(development-mode only)*
1011
* [Usage](#runtime-settings-usage)
1112
* [`Reusability.disableGloballyInDev()`](#reusabilitydisablegloballyindev)
@@ -161,6 +162,59 @@ object CustomConfig extends ScalaJsReactConfig.Defaults {
161162
```
162163

163164

165+
# `.test.warnings.react`
166+
167+
When using `ReactTestUtils`, this setting can be used to catch React warnings and turn them into exceptions.
168+
169+
### Usage:
170+
171+
```
172+
sbt -Djapgolly.scalajs.react.test.warnings.react=warn|fatal
173+
```
174+
175+
| Setting | Outcome |
176+
| -- | -- |
177+
| `warn` (default) | Print warnings and move on |
178+
| `fatal` | Throw warnings as exceptions |
179+
180+
### Example:
181+
182+
```
183+
sbt -Djapgolly.scalajs.react.test.warnings.react=fatal
184+
```
185+
186+
```scala
187+
package com.example
188+
189+
import japgolly.scalajs.react._
190+
import japgolly.scalajs.react.test._
191+
import japgolly.scalajs.react.vdom.html_<^._
192+
import utest._
193+
194+
object ExampleTest extends TestSuite {
195+
196+
override def tests = Tests {
197+
"example" - {
198+
val comp = ScalaFnComponent[Int](i => <.p(<.td(s"i = $i")))
199+
ReactTestUtils.withRenderedIntoBody(comp(123)).withParent { m =>
200+
val html = m.outerHTML
201+
assert(html == "<p><td>i = 123</td></p>")
202+
}
203+
}
204+
}
205+
}
206+
```
207+
208+
Running the above test will fail with this error message:
209+
210+
```
211+
scala.scalajs.js.JavaScriptException: Warning: validateDOMNesting(...): <td> cannot appear as a child of <p>.
212+
at td
213+
at p
214+
at $c_sjs_js_Any$.fromFunction1__F1__sjs_js_Function1
215+
```
216+
217+
164218
# Runtime Settings: Usage
165219

166220
Runtime settings are designed for use in development-mode only (`fastOptJS`).

doc/TESTING.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ for how to write tests for real-world scalajs-react applications.
1616
- [`Testing props changes`](#testing-props-changes)
1717
- [`ReactTestVar`](#reacttestvar)
1818
- [`Test Scripts`](#test-scripts)
19+
- [Fatal React warnings](#fatal-react-warnings)
1920

2021
Setup
2122
=====
@@ -223,3 +224,45 @@ In case you missed the notice at the top of the file, that functionality is prov
223224

224225
See [this example](https://github.com/japgolly/test-state/tree/master/example-react)
225226
for how to write tests for real-world scalajs-react applications.
227+
228+
229+
Fatal React warnings
230+
====================
231+
232+
The easiest way to make `ReactTestUtils` to turn React warnings into runtime exceptions,
233+
is via a [config option](./CONFIG.md#testwarningsreact).
234+
235+
Alternatively, you can do any of the following...
236+
237+
* Wrapping a test
238+
239+
```scala
240+
import japgolly.scalajs.react.test.ReactTestUtilsConfig
241+
ReactTestUtilsConfig.AroundReact.fatalReactWarnings {
242+
// test code here
243+
}
244+
```
245+
246+
* Installing for all `ReactTestUtils` usage
247+
248+
```scala
249+
import japgolly.scalajs.react.test.ReactTestUtilsConfig
250+
ReactTestUtilsConfig.aroundReact.set(
251+
ReactTestUtilsConfig.AroundReact.fatalReactWarnings)
252+
```
253+
254+
* Installing outside of test code
255+
256+
```scala
257+
import japgolly.scalajs.react.util.ConsoleHijack
258+
ConsoleHijack.fatalReactWarnings.install()
259+
```
260+
261+
* Wrapping non-test code
262+
263+
```scala
264+
import japgolly.scalajs.react.util.ConsoleHijack
265+
ConsoleHijack.fatalReactWarnings {
266+
// code here
267+
}
268+
```

doc/changelog/2.0.0.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,10 @@ You can run the script in the Migration section at the bottom of the page to aut
355355
356356
# Changes in RC4
357357
358+
* Support turning React warnings into runtime exceptions. There are a few ways to do this:
359+
* Via a [new config option](../CONFIG.md#testwarningsreact) for `ReactTestUtils`
360+
* [Manually](../TESTING.md#fatal-react-warnings)
361+
358362
* Document existing `debounce` methods to clarify you need to save them as a `val` and reuse them
359363
* Add:
360364
* `AsyncCallback.debounce(duration): AsyncCallback[Unit]`

downstream-tests/js/src/test/scala/downstream/RuntimeTests.scala

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,22 @@ import cats.effect.IO
44
import japgolly.microlibs.compiletime.CompileTimeInfo
55
import japgolly.microlibs.testutil.TestUtil._
66
import japgolly.scalajs.react._
7+
import japgolly.scalajs.react.vdom.html_<^._
78
import japgolly.scalajs.react.test.ReactTestUtils._
89
import japgolly.scalajs.react.util.JsUtil
10+
import org.scalajs.dom.console
911
import scala.scalajs.js
1012
import scala.scalajs.LinkingInfo.developmentMode
1113
import scala.util.Try
1214
import utest._
15+
import japgolly.scalajs.react.test.ReactTestUtils
1316

1417
object RuntimeTests extends TestSuite {
1518

16-
val compNameAuto = CompileTimeInfo.sysProp("japgolly.scalajs.react.component.names.implicit")
17-
val compNameAll = CompileTimeInfo.sysProp("japgolly.scalajs.react.component.names.all")
18-
val configClass = CompileTimeInfo.sysProp("japgolly.scalajs.react.config.class")
19+
val compNameAuto = CompileTimeInfo.sysProp("japgolly.scalajs.react.component.names.implicit")
20+
val compNameAll = CompileTimeInfo.sysProp("japgolly.scalajs.react.component.names.all")
21+
val configClass = CompileTimeInfo.sysProp("japgolly.scalajs.react.config.class")
22+
val testWarningsReact = CompileTimeInfo.sysProp("japgolly.scalajs.react.test.warnings.react")
1923

2024
val dsCfg1 = configClass.contains("downstream.DownstreamConfig1")
2125
val dsCfg2 = configClass.contains("downstream.DownstreamConfig2")
@@ -82,5 +86,25 @@ object RuntimeTests extends TestSuite {
8286
.map(_.get)
8387
.unsafeToFuture()
8488
}
89+
90+
"testWarnings" - {
91+
92+
"react" - {
93+
val c = ScalaFnComponent[Int](i => <.p(<.td(s"i = $i")))
94+
val t = Try(ReactTestUtils.withRenderedIntoBody(c(123))(_ => ()))
95+
assertEq(t.isFailure, testWarningsReact.contains("react"))
96+
}
97+
98+
"unlreated" - {
99+
val c = ScalaFnComponent[Int](i => <.p(s"i = $i"))
100+
val t = Try(ReactTestUtils.withRenderedIntoBody(c(123)) { _ =>
101+
console.info(".")
102+
console.log(".")
103+
console.warn(".")
104+
console.error(".")
105+
})
106+
assertEq(t.isFailure, false)
107+
}
108+
}
85109
}
86110
}

downstream-tests/run

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ skip2=skip2
66

77
# Tests
88
all_tests_values=(
9-
"-Ddownstream_tests.enableJSCE"
9+
"-Ddownstream_tests.enableJSCE -Djapgolly.scalajs.react.test.warnings.react=fatal"
1010
"-Djapgolly.scalajs.react.config.class=downstream.DownstreamConfig1 -Ddownstream_tests.reusability.dev=disable $skip2"
1111
"-Djapgolly.scalajs.react.component.names.implicit=blank -Djapgolly.scalajs.react.component.names.all=allow"
1212
"-Djapgolly.scalajs.react.component.names.implicit=short -Ddownstream_tests.reusability.dev=disable"

project/Build.scala

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ object ScalaJsReact {
3535
ghpagesMacros,
3636
scalafixRules,
3737
tests,
38+
testUtilMacros,
3839
testUtil,
3940
util,
4041
utilCatsEffect,
@@ -222,12 +223,19 @@ object ScalaJsReact {
222223
)
223224

224225
lazy val testUtil = project
225-
.dependsOn(coreGeneric, extra, facadeTest)
226+
.dependsOn(facadeTest, testUtilMacros, extra)
226227
.configure(commonSettings, publicationSettings, hasNoTests, effectGenericModule)
227228
.settings(
228229
moduleName := "test",
229230
)
230231

232+
lazy val testUtilMacros = project
233+
.dependsOn(coreGeneric)
234+
.configure(commonSettings, publicationSettings, definesMacros, hasNoTests, effectGenericModule)
235+
.settings(
236+
moduleName := "test-macros",
237+
)
238+
231239
lazy val util = project
232240
.dependsOn(utilFallbacks % Provided)
233241
.configure(commonSettings, publicationSettings, hasNoTests, disableScalaDoc3, prohibitDefaultEffects)

0 commit comments

Comments
 (0)