diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..dbece55
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,41 @@
+sudo: false
+language: scala
+scala:
+ - 2.10.6
+ - 2.11.11
+ - 2.12.2
+jdk:
+ - oraclejdk8
+env:
+ - JSDOM_VERSION=9.12.0
+ - JSDOM_VERSION=10.0.0
+install:
+ # The default ivy resolution takes way too much time, and times out Travis builds.
+ # We use coursier instead.
+ - mkdir -p $HOME/.sbt/0.13/plugins/
+ - echo 'addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-RC3")' > $HOME/.sbt/0.13/plugins/coursier.sbt
+ # We need a recent version of Node.js for jsdom
+ - nvm install 6
+ - nvm use 6
+ - node --version
+ # Of course we need jsdom
+ - npm install jsdom@$JSDOM_VERSION
+ # While there is no published version of Scala.js 1.x, we have to locally build a snapshot.
+ - git clone https://github.com/scala-js/scala-js.git
+ - cd scala-js
+ - git checkout d25fa8bba708977c68c093fdbc50958368f9602f
+ - sbt ++$TRAVIS_SCALA_VERSION compiler/publishLocal jUnitPlugin/publishLocal library/publishLocal testInterface/publishLocal jUnitRuntime/publishLocal ir/publishLocal tools/publishLocal jsEnvs/publishLocal jsEnvsTestKit/publishLocal nodeJSEnv/publishLocal
+ - sbt ++2.10.6 ir/publishLocal tools/publishLocal jsEnvs/publishLocal nodeJSEnv/publishLocal testAdapter/publishLocal sbtPlugin/publishLocal
+ - cd ..
+script:
+ - sbt ++$TRAVIS_SCALA_VERSION scalajs-env-jsdom-nodejs/test scalajs-env-jsdom-nodejs/doc
+ - sbt ++$TRAVIS_SCALA_VERSION test-project/run test-project/test
+cache:
+ directories:
+ - $HOME/.ivy2/cache
+ - $HOME/.sbt
+ - $HOME/.coursier/cache
+before_cache:
+ - find $HOME/.ivy2/cache -name "ivydata-*.properties" -print -delete
+ - find $HOME/.sbt -name "*.lock" -print -delete
+ - rm $HOME/.sbt/0.13/plugins/coursier.sbt
diff --git a/build.sbt b/build.sbt
new file mode 100644
index 0000000..511eb75
--- /dev/null
+++ b/build.sbt
@@ -0,0 +1,88 @@
+inThisBuild(Seq(
+ version := "0.1.0-SNAPSHOT",
+ organization := "org.scala-js",
+
+ crossScalaVersions := Seq("2.10.6", "2.11.11", "2.12.2"),
+ scalaVersion := "2.11.11",
+ scalacOptions ++= Seq("-deprecation", "-feature", "-Xfatal-warnings"),
+
+ homepage := Some(url("https://www.scala-js.org/")),
+ licenses += ("BSD New",
+ url("https://github.com/scala-js/scala-js-env-jsdom-nodejs/blob/master/LICENSE")),
+ scmInfo := Some(ScmInfo(
+ url("https://github.com/scala-js/scala-js-env-jsdom-nodejs"),
+ "scm:git:git@github.com:scala-js/scala-js-env-jsdom-nodejs.git",
+ Some("scm:git:git@github.com:scala-js/scala-js-env-jsdom-nodejs.git")))
+))
+
+val commonSettings = Def.settings(
+ // Scaladoc linking
+ apiURL := {
+ val name = moduleName.value
+ val v = version.value
+ Some(url(s"https://www.scala-js.org/api/$name/$v/"))
+ },
+ autoAPIMappings := true,
+
+ publishMavenStyle := true,
+ publishTo := {
+ val nexus = "https://oss.sonatype.org/"
+ if (isSnapshot.value)
+ Some("snapshots" at nexus + "content/repositories/snapshots")
+ else
+ Some("releases" at nexus + "service/local/staging/deploy/maven2")
+ },
+ pomExtra := (
+
+
+ sjrd
+ Sébastien Doeraene
+ https://github.com/sjrd/
+
+
+ gzm0
+ Tobias Schlatter
+ https://github.com/gzm0/
+
+
+ nicolasstucki
+ Nicolas Stucki
+ https://github.com/nicolasstucki/
+
+
+ ),
+ pomIncludeRepository := { _ => false }
+)
+
+lazy val root: Project = project.in(file(".")).
+ settings(
+ publishArtifact in Compile := false,
+ publish := {},
+ publishLocal := {},
+
+ clean := clean.dependsOn(
+ clean in `scalajs-env-jsdom-nodejs`,
+ clean in `test-project`
+ ).value
+ )
+
+lazy val `scalajs-env-jsdom-nodejs`: Project = project.in(file("jsdom-nodejs-env")).
+ settings(
+ commonSettings,
+
+ libraryDependencies ++= Seq(
+ "org.scala-js" %% "scalajs-js-envs" % scalaJSVersion,
+ "org.scala-js" %% "scalajs-nodejs-env" % scalaJSVersion,
+
+ "com.novocode" % "junit-interface" % "0.11" % "test",
+ "org.scala-js" %% "scalajs-js-envs-test-kit" % scalaJSVersion % "test"
+ )
+ )
+
+lazy val `test-project`: Project = project.
+ enablePlugins(ScalaJSPlugin).
+ enablePlugins(ScalaJSJUnitPlugin).
+ settings(
+ scalaJSUseMainModuleInitializer := true,
+ jsEnv := new org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv()
+ )
diff --git a/jsdom-nodejs-env/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala b/jsdom-nodejs-env/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala
new file mode 100644
index 0000000..ac6fbab
--- /dev/null
+++ b/jsdom-nodejs-env/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala
@@ -0,0 +1,185 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js JS envs **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2017, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+package org.scalajs.jsenv.jsdomnodejs
+
+import scala.collection.immutable
+
+import java.io.OutputStream
+
+import org.scalajs.core.tools.io._
+import org.scalajs.jsenv._
+import org.scalajs.jsenv.nodejs.AbstractNodeJSEnv
+
+import org.scalajs.core.ir.Utils.escapeJS
+
+class JSDOMNodeJSEnv(config: JSDOMNodeJSEnv.Config) extends AbstractNodeJSEnv {
+
+ def this() = this(JSDOMNodeJSEnv.Config())
+
+ protected def vmName: String = "Node.js with JSDOM"
+
+ protected def executable: String = config.executable
+
+ override protected def args: immutable.Seq[String] = config.args
+
+ override protected def env: Map[String, String] = config.env
+
+ // TODO We might want to make this configurable - not sure why it isn't
+ override protected def wantSourceMap: Boolean = false
+
+ override def jsRunner(files: Seq[VirtualJSFile]): JSRunner =
+ new DOMNodeRunner(files)
+
+ override def asyncRunner(files: Seq[VirtualJSFile]): AsyncJSRunner =
+ new AsyncDOMNodeRunner(files)
+
+ override def comRunner(files: Seq[VirtualJSFile]): ComJSRunner =
+ new ComDOMNodeRunner(files)
+
+ protected class DOMNodeRunner(files: Seq[VirtualJSFile])
+ extends ExtRunner(files) with AbstractDOMNodeRunner
+
+ protected class AsyncDOMNodeRunner(files: Seq[VirtualJSFile])
+ extends AsyncExtRunner(files) with AbstractDOMNodeRunner
+
+ protected class ComDOMNodeRunner(files: Seq[VirtualJSFile])
+ extends AsyncDOMNodeRunner(files) with NodeComJSRunner
+
+ protected trait AbstractDOMNodeRunner extends AbstractNodeRunner {
+
+ protected def codeWithJSDOMContext(): Seq[VirtualJSFile] = {
+ val scriptsPaths = getScriptsJSFiles().map {
+ case file: FileVirtualFile => file.path
+ case file => libCache.materialize(file).getAbsolutePath
+ }
+ val scriptsURIs =
+ scriptsPaths.map(path => new java.io.File(path).toURI.toASCIIString)
+ val scriptsURIsAsJSStrings = scriptsURIs.map('"' + escapeJS(_) + '"')
+ val jsDOMCode = {
+ s"""
+ |(function () {
+ | var jsdom;
+ | try {
+ | jsdom = require("jsdom/lib/old-api.js"); // jsdom >= 10.x
+ | } catch (e) {
+ | jsdom = require("jsdom"); // jsdom <= 9.x
+ | }
+ |
+ | var virtualConsole = jsdom.createVirtualConsole()
+ | .sendTo(console, { omitJsdomErrors: true });
+ | virtualConsole.on("jsdomError", function (error) {
+ | /* This inelegant if + console.error is the only way I found
+ | * to make sure the stack trace of the original error is
+ | * printed out.
+ | */
+ | if (error.detail && error.detail.stack)
+ | console.error(error.detail.stack);
+ |
+ | // Throw the error anew to make sure the whole execution fails
+ | throw error;
+ | });
+ |
+ | jsdom.env({
+ | html: "",
+ | url: "http://localhost/",
+ | virtualConsole: virtualConsole,
+ | created: function (error, window) {
+ | if (error == null) {
+ | window["__ScalaJSEnv"] = __ScalaJSEnv;
+ | window["scalajsCom"] = global.scalajsCom;
+ | } else {
+ | throw error;
+ | }
+ | },
+ | scripts: [${scriptsURIsAsJSStrings.mkString(", ")}]
+ | });
+ |})();
+ |""".stripMargin
+ }
+ Seq(new MemVirtualJSFile("codeWithJSDOMContext.js").withContent(jsDOMCode))
+ }
+
+ /** All the JS files that are passed to the VM.
+ *
+ * This method can overridden to provide custom behavior in subclasses.
+ *
+ * This method is overridden in `JSDOMNodeJSEnv` so that user-provided
+ * JS files (excluding "init" files) are executed as *scripts* within the
+ * jsdom environment, rather than being directly executed by the VM.
+ *
+ * The value returned by this method in `JSDOMNodeJSEnv` is
+ * `initFiles() ++ customInitFiles() ++ codeWithJSDOMContext()`.
+ */
+ override protected def getJSFiles(): Seq[VirtualJSFile] =
+ initFiles() ++ customInitFiles() ++ codeWithJSDOMContext()
+
+ /** JS files to be loaded via scripts in the jsdom environment.
+ *
+ * This method can be overridden to provide a different list of scripts.
+ *
+ * The default value in `JSDOMNodeJSEnv` is `files`.
+ */
+ protected def getScriptsJSFiles(): Seq[VirtualJSFile] =
+ files
+
+ // Send code to Stdin
+ override protected def sendVMStdin(out: OutputStream): Unit = {
+ /* Do not factor this method out into AbstractNodeRunner or when mixin in
+ * the traits it would use AbstractExtRunner.sendVMStdin due to
+ * linearization order.
+ */
+ sendJS(getJSFiles(), out)
+ }
+ }
+}
+
+object JSDOMNodeJSEnv {
+ final class Config private (
+ val executable: String,
+ val args: List[String],
+ val env: Map[String, String]
+ ) {
+ private def this() = {
+ this(
+ executable = "node",
+ args = Nil,
+ env = Map.empty
+ )
+ }
+
+ def withExecutable(executable: String): Config =
+ copy(executable = executable)
+
+ def withArgs(args: List[String]): Config =
+ copy(args = args)
+
+ def withEnv(env: Map[String, String]): Config =
+ copy(env = env)
+
+ private def copy(
+ executable: String = executable,
+ args: List[String] = args,
+ env: Map[String, String] = env
+ ): Config = {
+ new Config(executable, args, env)
+ }
+ }
+
+ object Config {
+ /** Returns a default configuration for a [[JSDOMNodeJSEnv]].
+ *
+ * The defaults are:
+ *
+ * - `executable`: `"node"`
+ * - `args`: `Nil`
+ * - `env`: `Map.empty`
+ */
+ def apply(): Config = new Config()
+ }
+}
diff --git a/jsdom-nodejs-env/src/test/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnvTest.scala b/jsdom-nodejs-env/src/test/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnvTest.scala
new file mode 100644
index 0000000..0957c6e
--- /dev/null
+++ b/jsdom-nodejs-env/src/test/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnvTest.scala
@@ -0,0 +1,23 @@
+package org.scalajs.jsenv.jsdomnodejs
+
+import org.scalajs.jsenv.test._
+
+import org.junit.Test
+import org.junit.Assert._
+
+class JSDOMNodeJSEnvTest extends TimeoutComTests {
+
+ protected def newJSEnv: JSDOMNodeJSEnv = new JSDOMNodeJSEnv()
+
+ @Test
+ def historyAPI: Unit = {
+ """|console.log(window.location.href);
+ |window.history.pushState({}, "", "/foo");
+ |console.log(window.location.href);
+ """.stripMargin hasOutput
+ """|http://localhost/
+ |http://localhost/foo
+ |""".stripMargin
+ }
+
+}
diff --git a/project/build.properties b/project/build.properties
new file mode 100644
index 0000000..64317fd
--- /dev/null
+++ b/project/build.properties
@@ -0,0 +1 @@
+sbt.version=0.13.15
diff --git a/project/plugins.sbt b/project/plugins.sbt
new file mode 100644
index 0000000..ab8517d
--- /dev/null
+++ b/project/plugins.sbt
@@ -0,0 +1,6 @@
+addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.0.0-SNAPSHOT")
+
+libraryDependencies += "org.scala-js" %% "scalajs-nodejs-env" % "1.0.0-SNAPSHOT"
+
+unmanagedSourceDirectories in Compile +=
+ baseDirectory.value.getParentFile / "jsdom-nodejs-env/src/main/scala"
diff --git a/test-project/src/main/scala/testproject/Lib.scala b/test-project/src/main/scala/testproject/Lib.scala
new file mode 100644
index 0000000..e6d0913
--- /dev/null
+++ b/test-project/src/main/scala/testproject/Lib.scala
@@ -0,0 +1,19 @@
+package testproject
+
+import scala.scalajs.js
+
+object Lib {
+
+ val document: js.Dynamic = js.Dynamic.global.document
+
+ def getElementsByTagName(name: String): js.Array[js.Dynamic] =
+ document.getElementsByTagName(name).asInstanceOf[js.Array[js.Dynamic]]
+
+ /** appends a
with the message to the document */
+ def appendDocument(msg: String): Unit = {
+ val elem = document.createElement("p")
+ elem.appendChild(document.createTextNode(msg))
+ document.body.appendChild(elem)
+ }
+
+}
diff --git a/test-project/src/main/scala/testproject/TestApp.scala b/test-project/src/main/scala/testproject/TestApp.scala
new file mode 100644
index 0000000..9c17e1f
--- /dev/null
+++ b/test-project/src/main/scala/testproject/TestApp.scala
@@ -0,0 +1,12 @@
+package testproject
+
+object TestApp {
+
+ def main(args: Array[String]): Unit = {
+ Lib.appendDocument("Hello World")
+ Lib.appendDocument("Still Here!")
+
+ println(Lib.getElementsByTagName("p").head.innerHTML)
+ }
+
+}
diff --git a/test-project/src/test/scala/testproject/LibTest.scala b/test-project/src/test/scala/testproject/LibTest.scala
new file mode 100644
index 0000000..1bbe022
--- /dev/null
+++ b/test-project/src/test/scala/testproject/LibTest.scala
@@ -0,0 +1,16 @@
+package testproject
+
+import scala.scalajs.js
+
+import org.junit.Test
+import org.junit.Assert._
+
+class LibTest {
+ @Test def dummy_library_should_append_an_element(): Unit = {
+ def count = Lib.getElementsByTagName("p").length
+
+ val oldCount = count
+ Lib.appendDocument("foo")
+ assertEquals(1, count - oldCount)
+ }
+}