Search code examples
scala.jsscalatags

org_scalajs_dom_raw_HTMLDocument(...).createRange is not a function


I'm upgrading scalatags from 0.6.7 to 0.9.3 as part of upgrading scalaJS from 0.6.x to 1.4.0.

I got the following error in some of my tests:

scala.scalajs.js.JavaScriptException: TypeError: $m_Lorg_scalajs_dom_package$(...).document__Lorg_scalajs_dom_raw_HTMLDocument(...).createRange is not a function

Tracing the code, I believe it occurs while executing line 141 of the following scalatags code in `scalatags.JsDom:

offending scalatags code

I extracted just the createRange call into a separate test and got the same error. "creating range" was printed; "created range" was not and it produced the same exception as above.

isolated test

createRange() is a native function.

Googling "createRange is not a function" yields a number of similar issues, all seem to be related to testing (but not with ScalaJS). Many of them indicate the "fix" is to monkey-patch document with your own version of createRange. Really?

I initially thought this was an issue with scalatags. Then I thought it's with the scalajs library. Now I'm thinking it's something with Node.js, although Google is not producing any smoking guns.

Suggestions on how to proceed? Try to monkey patch document?


Solution

  • Summary: jsdom appears to be missing the document.createRange function when using Node.js for testing. Others in other languages have similar problems.

    The following monkey patch worked for me. After developing this, I noticed that Facade Types has a section on monkey typing.

    Also, the library code that tickled this bug (scalatags) actually calls document.createRange().createContextualFragment(v). So I needed to provide something for that as well.

      import scala.scalajs.js
      import org.scalajs.dom.document
      js.Dynamic.global.document.createRange = () ⇒
        js.Dynamic.literal(
          setStart = () => js.Dynamic.literal(),
          setEnd   = () => js.Dynamic.literal(),
          commonAncestorContainer = js.Dynamic.literal(
            nodeName      = "BODY",
            ownerDocument = document
          ),
          createContextualFragment = (v: String) ⇒ {
            val p = document.createElement("p")
            p.appendChild(document.createTextNode(v))
          }
        )