Skip to content

Commit eb49c7d

Browse files
authored
Merge pull request #45 from sjrd/fix-react-development-mode
Fix #42: Hack around React's development mode error triggering hack.
2 parents 9dde29e + c814a01 commit eb49c7d

File tree

3 files changed

+120
-1
lines changed

3 files changed

+120
-1
lines changed

build.sbt

+9-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,15 @@ lazy val `scalajs-env-jsdom-nodejs`: Project = project.in(file("jsdom-nodejs-env
7575
"org.scala-js" %% "scalajs-env-nodejs" % scalaJSVersion,
7676

7777
"com.novocode" % "junit-interface" % "0.11" % "test",
78-
"org.scala-js" %% "scalajs-js-envs-test-kit" % scalaJSVersion % "test"
78+
"org.scala-js" %% "scalajs-js-envs-test-kit" % scalaJSVersion % "test",
79+
80+
/* See JSDOMNodeJSEnvTest.reactUnhandledExceptionHack.
81+
* We use intransitive() because we do not need the transitive
82+
* dependencies of these webjars, and one of them actually fails to
83+
* resolve (see https://github.com/webjars/webjars/issues/1789).
84+
*/
85+
"org.webjars.npm" % "react" % "16.13.1" % "test" intransitive(),
86+
"org.webjars.npm" % "react-dom" % "16.13.1" % "test" intransitive(),
7987
)
8088
)
8189

jsdom-nodejs-env/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala

+9
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,15 @@ class JSDOMNodeJSEnv(config: JSDOMNodeJSEnv.Config) extends JSEnv {
8484
| var virtualConsole = new jsdom.VirtualConsole()
8585
| .sendTo(console, { omitJSDOMErrors: true });
8686
| virtualConsole.on("jsdomError", function (error) {
87+
| /* #42 Counter-hack the hack that React's development mode uses
88+
| * to bypass browsers' debugging tools. If we detect that we are
89+
| * called from that hack, we do nothing.
90+
| */
91+
| var isWithinReactsInvokeGuardedCallbackDevHack_issue42 =
92+
| new Error("").stack.indexOf("invokeGuardedCallbackDev") >= 0;
93+
| if (isWithinReactsInvokeGuardedCallbackDevHack_issue42)
94+
| return;
95+
|
8796
| try {
8897
| // Display as much info about the error as possible
8998
| if (error.detail && error.detail.stack) {

jsdom-nodejs-env/src/test/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnvTest.scala

+102
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
package org.scalajs.jsenv.jsdomnodejs
22

3+
import java.nio.charset.StandardCharsets
4+
import java.nio.file.{Files, Path}
5+
36
import scala.concurrent.duration._
47

8+
import com.google.common.jimfs.Jimfs
9+
510
import org.junit.Test
611

12+
import org.scalajs.jsenv.Input
713
import org.scalajs.jsenv.test.kit.TestKit
814

915
class JSDOMNodeJSEnvTest {
16+
import JSDOMNodeJSEnvTest._
17+
1018
private val kit = new TestKit(new JSDOMNodeJSEnv, 1.minute)
1119

1220
@Test
@@ -21,4 +29,98 @@ class JSDOMNodeJSEnvTest {
2129
.expectOut("http://localhost/foo\n")
2230
}
2331
}
32+
33+
@Test
34+
def reactUnhandledExceptionHack_issue42: Unit = {
35+
val code =
36+
"""
37+
|const rootElement = document.createElement("div");
38+
|document.body.appendChild(rootElement);
39+
|
40+
|class ThrowingComponent extends React.Component {
41+
| render() {
42+
| throw new Error("boom");
43+
| }
44+
|}
45+
|
46+
|class ErrorBoundary extends React.Component {
47+
| constructor(props) {
48+
| super(props);
49+
| this.state = { hasError: false };
50+
| }
51+
|
52+
| componentDidCatch(error, info) {
53+
| this.setState({error: error.message, hasError: true});
54+
| }
55+
|
56+
| render() {
57+
| if (this.state.hasError) {
58+
| console.log("render-error");
59+
| return React.createElement("p", null,
60+
| `Caught error: ${this.state.error}`);
61+
| } else {
62+
| return this.props.children;
63+
| }
64+
| }
65+
|}
66+
|
67+
|class MyMainComponent extends React.Component {
68+
| render() {
69+
| console.log("two");
70+
| return React.createElement(ErrorBoundary, null,
71+
| React.createElement(ThrowingComponent)
72+
| );
73+
| }
74+
|}
75+
|
76+
|console.log("begin");
77+
|
78+
|const mounted = ReactDOM.render(
79+
| React.createElement(ErrorBoundary, null,
80+
| React.createElement(ThrowingComponent, null)
81+
| ),
82+
| rootElement
83+
|);
84+
|
85+
|console.log(document.querySelector("p").textContent);
86+
|
87+
|console.log("end");
88+
""".stripMargin
89+
90+
kit.withRun(ReactJSFiles :+ codeToInput(code)) {
91+
_.expectOut("begin\nrender-error\nCaught error: boom\nend\n")
92+
.succeeds()
93+
}
94+
}
95+
}
96+
97+
object JSDOMNodeJSEnvTest {
98+
private lazy val ReactJSFiles: List[Input] = {
99+
val fs = Jimfs.newFileSystem()
100+
val reactFile = copyResource(
101+
"/META-INF/resources/webjars/react/16.13.1/umd/react.development.js",
102+
fs.getPath("react.development.js"))
103+
val reactDOMFile = copyResource(
104+
"/META-INF/resources/webjars/react-dom/16.13.1/umd/react-dom.development.js",
105+
fs.getPath("react-dom.development.js"))
106+
List(reactFile, reactDOMFile).map(Input.Script(_))
107+
}
108+
109+
private def copyResource(name: String, out: Path): out.type = {
110+
val inputStream = getClass().getResourceAsStream(name)
111+
assert(inputStream != null, s"couldn't load $name from resources")
112+
try {
113+
Files.copy(inputStream, out)
114+
} finally {
115+
inputStream.close()
116+
}
117+
out
118+
}
119+
120+
private def codeToInput(code: String): Input = {
121+
val p = Files.write(
122+
Jimfs.newFileSystem().getPath("testScript.js"),
123+
code.getBytes(StandardCharsets.UTF_8))
124+
Input.Script(p)
125+
}
24126
}

0 commit comments

Comments
 (0)