diff --git a/.travis.yml b/.travis.yml
index 922d59c971..6f7658c71e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,6 @@
language: scala
script:
- - sbt package scalajs-test/test
+ - sbt package testing/test scalajs-test/test
after_success:
- if [[ "${PUBLISH_ENABLED}" == "true" && "${TRAVIS_PULL_REQUEST}" == "false" && "${TRAVIS_BRANCH}" == "master" && "${PUBLISH_USER}" != "" && "${PUBLISH_PASS}" != "" ]]; then sbt publish; fi
scala:
diff --git a/examples/testing/build.sbt b/examples/testing/build.sbt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/examples/testing/src/main/scala/ElementCreator.scala b/examples/testing/src/main/scala/ElementCreator.scala
new file mode 100644
index 0000000000..ccb3600592
--- /dev/null
+++ b/examples/testing/src/main/scala/ElementCreator.scala
@@ -0,0 +1,7 @@
+import scala.scalajs.js.Dynamic.global
+
+object ElementCreator {
+ val jQ = global.jQuery
+
+ def create() = jQ("body").append(jQ("
Test
"))
+}
diff --git a/examples/testing/src/test/scala/ElementCreatorTest.scala b/examples/testing/src/test/scala/ElementCreatorTest.scala
new file mode 100644
index 0000000000..d513fc67c8
--- /dev/null
+++ b/examples/testing/src/test/scala/ElementCreatorTest.scala
@@ -0,0 +1,26 @@
+import scala.scalajs.js
+import scala.scalajs.js.Dynamic.global
+import scala.scalajs.test.JasmineTest
+
+object ElementCreatorTest extends JasmineTest {
+
+ // ElementCreator expects jquery to be present
+ global.importScripts("jquery.js")
+
+ describe("ElementCreator") {
+
+ it("should be able to create an element in the body") {
+ // create the element
+ ElementCreator.create()
+
+ // jquery would make this easier, but I wanted to
+ // only use pure html in the test itself
+ val body = global.document.getElementsByTagName("body")
+ .asInstanceOf[js.Array[js.Dynamic]].head
+
+ // the Scala.js DOM API would make this easier
+ expect(body.firstChild.tagName.toString == "H1").toBeTruthy
+ expect(body.firstChild.innerHTML.toString == "Test").toBeTruthy
+ }
+ }
+}
diff --git a/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Jasmine.scala b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Jasmine.scala
new file mode 100644
index 0000000000..b215835eb6
--- /dev/null
+++ b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Jasmine.scala
@@ -0,0 +1,13 @@
+package org.scalajs.jasmine
+
+import scala.scalajs.js
+
+object Jasmine extends js.GlobalScope {
+ def describe(name: String, suite: js.Function0[_]): Unit = ???
+ def it(title: String, test: js.Function0[_]): Unit = ???
+ def xdescribe(name: String, suite: js.Function0[_]): Unit = ???
+ def xit(title: String, test: js.Function0[_]): Unit = ???
+ def beforeEach(block: js.Function0[_]): Unit = ???
+ def afterEach(block: js.Function0[_]): Unit = ???
+ def expect(exp: js.Any): JasmineExpectation = ???
+}
diff --git a/test/src/test/scala/scala/scalajs/test/JasmineExpectation.scala b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/JasmineExpectation.scala
similarity index 52%
rename from test/src/test/scala/scala/scalajs/test/JasmineExpectation.scala
rename to jasmine-test-framework/src/main/scala/org/scalajs/jasmine/JasmineExpectation.scala
index 6789c79038..a4f7d268d8 100644
--- a/test/src/test/scala/scala/scalajs/test/JasmineExpectation.scala
+++ b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/JasmineExpectation.scala
@@ -1,11 +1,4 @@
-/* __ *\
-** ________ ___ / / ___ __ ____ Scala.js Test Suite **
-** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
-** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
-** /____/\___/_/ |_/____/_/ | |__/ /____/ **
-** |/____/ **
-\* */
-package scala.scalajs.test
+package org.scalajs.jasmine
import scala.scalajs.js
import java.util.regex.Pattern
diff --git a/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Results.scala b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Results.scala
new file mode 100644
index 0000000000..1053123a60
--- /dev/null
+++ b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Results.scala
@@ -0,0 +1,13 @@
+package org.scalajs.jasmine
+
+import scala.scalajs.js
+
+trait Result extends js.Object {
+ def `type`: js.String = ???
+ val trace: js.Dynamic = ???
+}
+
+trait ExpectationResult extends Result {
+ def passed(): js.Boolean = ???
+ val message: js.String = ???
+}
diff --git a/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Spec.scala b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Spec.scala
new file mode 100644
index 0000000000..47d284aefb
--- /dev/null
+++ b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Spec.scala
@@ -0,0 +1,9 @@
+package org.scalajs.jasmine
+
+import scala.scalajs.js
+
+trait Spec extends js.Object {
+ def results(): SpecResults = ???
+ val description: js.String = ???
+ val suite: Suite = ???
+}
diff --git a/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/SpecResults.scala b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/SpecResults.scala
new file mode 100644
index 0000000000..0a74c8b5b2
--- /dev/null
+++ b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/SpecResults.scala
@@ -0,0 +1,8 @@
+package org.scalajs.jasmine
+
+import scala.scalajs.js
+
+trait SpecResults extends js.Object {
+ def passed(): js.Boolean = ???
+ def getItems(): js.Array[Result] = ???
+}
diff --git a/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Suite.scala b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Suite.scala
new file mode 100644
index 0000000000..2d5ad73a3e
--- /dev/null
+++ b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Suite.scala
@@ -0,0 +1,8 @@
+package org.scalajs.jasmine
+
+import scala.scalajs.js
+
+trait Suite extends js.Object {
+ def results(): SuiteResults = ???
+ val description: js.String = ???
+}
diff --git a/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/SuiteResults.scala b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/SuiteResults.scala
new file mode 100644
index 0000000000..789de9c00d
--- /dev/null
+++ b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/SuiteResults.scala
@@ -0,0 +1,9 @@
+package org.scalajs.jasmine
+
+import scala.scalajs.js
+
+trait SuiteResults extends js.Object {
+ val passedCount: js.Number = ???
+ val failedCount: js.Number = ???
+ val totalCount: js.Number = ???
+}
diff --git a/test/src/test/scala/scala/scalajs/test/ScalaJSTest.scala b/jasmine-test-framework/src/main/scala/scala/scalajs/test/JasmineTest.scala
similarity index 85%
rename from test/src/test/scala/scala/scalajs/test/ScalaJSTest.scala
rename to jasmine-test-framework/src/main/scala/scala/scalajs/test/JasmineTest.scala
index 841a90ef63..184f935f24 100644
--- a/test/src/test/scala/scala/scalajs/test/ScalaJSTest.scala
+++ b/jasmine-test-framework/src/main/scala/scala/scalajs/test/JasmineTest.scala
@@ -1,16 +1,20 @@
/* __ *\
-** ________ ___ / / ___ __ ____ Scala.js Test Suite **
+** ________ ___ / / ___ __ ____ Scala.js Test Framework **
** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
** /____/\___/_/ |_/____/_/ | |__/ /____/ **
** |/____/ **
\* */
+
+
package scala.scalajs.test
import scala.scalajs.js
import java.util.regex.Pattern
+import org.scalajs.jasmine.Jasmine
+import org.scalajs.jasmine.JasmineExpectation
-class ScalaJSTest {
+class JasmineTest extends Test {
def describe(name: String)(suite: => Unit): Unit = Jasmine.describe(name, suite _)
def it(title: String)(test: => Unit): Unit = Jasmine.it(title, test _)
def xdescribe(name: String)(suite: => Unit): Unit = Jasmine.xdescribe(name, suite _)
diff --git a/jasmine-test-framework/src/main/scala/scala/scalajs/test/JasmineTestFramework.scala b/jasmine-test-framework/src/main/scala/scala/scalajs/test/JasmineTestFramework.scala
new file mode 100644
index 0000000000..7ff9c6ad20
--- /dev/null
+++ b/jasmine-test-framework/src/main/scala/scala/scalajs/test/JasmineTestFramework.scala
@@ -0,0 +1,168 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js Test Framework **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.test
+
+import scala.scalajs.js
+import scala.scalajs.js.Dynamic.global
+import scala.scalajs.js.JavaScriptException
+
+import org.scalajs.jasmine.ExpectationResult
+import org.scalajs.jasmine.Result
+import org.scalajs.jasmine.Spec
+import org.scalajs.jasmine.Suite
+
+object JasmineTestFramework extends TestFramework {
+
+ /* Stub-out timer methods used by Jasmine and not provided by Rhino. */
+ if (!global.setTimeout) {
+ global.setTimeout = scalaJSStub("setTimeout")
+ global.clearTimeout = scalaJSStub("clearTimeout")
+ global.setInterval = scalaJSStub("setInterval")
+ global.clearInterval = scalaJSStub("clearInterval")
+ }
+
+ def scalaJSStub(name: String): js.Function = { () =>
+ global.console.log("Stub for " + name + " called")
+ }
+
+ // make sure jasmine is loaded
+ global.importScripts("jasmine.js")
+
+ def runTests(testOutput: TestOutput)(tests: => Unit): Unit = {
+ val jasmine = global.jasmine
+ val reporter = new JasmineTestFramework(testOutput)
+
+ try {
+ tests
+
+ val jasmineEnv = jasmine.getEnv()
+ jasmineEnv.addReporter(reporter.asInstanceOf[js.Any])
+ jasmineEnv.updateInterval = 0
+ jasmineEnv.execute()
+ } catch {
+ case JavaScriptException(exception) =>
+ val stack = exception.asInstanceOf[js.Dynamic].stack
+ testOutput.error("Problem executing code in tests: " + exception,
+ reporter.getScriptStack(stack))
+ }
+ }
+}
+
+class JasmineTestFramework(testOutput: TestOutput) {
+ private var currentSuite: Suite = _
+
+ def reportRunnerStarting(): Unit = {
+ testOutput.log.info("")
+ }
+
+ def reportSpecStarting(spec: Spec): Unit = {
+ if (currentSuite != spec.suite) {
+ currentSuite = spec.suite
+ info(currentSuite.description)
+ }
+ }
+
+ def reportSpecResults(spec: Spec): Unit = {
+ val results = spec.results()
+ val description = spec.description
+
+ if (results.passed) {
+ testOutput.succeeded(s" $success $description")
+ } else {
+ error(s" $failure $description")
+
+ results.getItems foreach displayResult
+ }
+ }
+
+ def reportSuiteResults(suite: Suite): Unit = {
+ var results = suite.results()
+
+ info("")
+ val title = "Total for suite " + suite.description
+ val message =
+ s"${results.totalCount} specs, ${results.failedCount} failure"
+
+ if (results.passedCount != results.totalCount) {
+ error(title)
+ errorWithInfoColor(message)
+ } else {
+ info(title)
+ infoWithInfoColor(message)
+ }
+ info("")
+ }
+
+ def reportRunnerResults(): Unit = {
+ // no need to report
+ }
+
+ private def info(str: String) =
+ testOutput.log.info(str)
+
+ private def infoWithInfoColor(str: String) =
+ info(withColor(testOutput.infoColor, str))
+
+ private def errorWithInfoColor(str: String) =
+ error(withColor(testOutput.infoColor, str))
+
+ private def error(msg: js.Any) =
+ testOutput.log.error(msg.toString)
+
+ private def withColor(color: String, message: String) =
+ testOutput.color(message, color)
+
+ private def sanitizeMessage(message: String) = {
+ val FilePattern = """^(.+?) [^ ]+\.js \(line \d+\)\.*?$""".r
+ val EvalPattern = """^(.+?) in eval.+\(eval\).+?\(line \d+\).*?$""".r
+
+ message match {
+ case FilePattern(originalMessage) => originalMessage
+ case EvalPattern(originalMessage) => originalMessage
+ case message => message
+ }
+ }
+
+ private def failure = withColor(testOutput.errorColor, "x")
+ private def success = withColor(testOutput.successColor, "+")
+
+ private def displayResult(result: Result) = {
+ (result.`type`: String) match {
+ case "log" =>
+ info(s" ${result.toString}")
+ case "expect" =>
+ val r = result.asInstanceOf[ExpectationResult]
+ if (!r.passed()) {
+ val message = sanitizeMessage(r.message)
+ val stack = getScriptStack(r.trace.stack)
+
+ if (stack.isEmpty) testOutput.failure(s" $message")
+ else testOutput.error(s" $message", stack)
+ }
+ }
+ }
+
+ private def getScriptStack(stack: js.Any): Array[ScriptStackElement] = {
+ if (stack.isInstanceOf[js.String]) {
+ val StackTracePattern = """^(.+?) ([^ ]+\.js):(\d+).*?$""".r
+ val stackString: String = stack.asInstanceOf[js.String]
+
+ stackString
+ .split("\n")
+ .map {
+ case StackTracePattern(originalMessage, fileName, lineNumber) =>
+ ScriptStackElement(fileName, "", lineNumber.toInt)
+ case unknown =>
+ throw JavaScriptException("Unknown stack element: " + unknown)
+ }
+ .takeWhile(e => !(e.fileName contains "jasmine.js"))
+ } else Array.empty
+ }
+}
diff --git a/library/src/main/scala/scala/scalajs/test/EventProxy.scala b/library/src/main/scala/scala/scalajs/test/EventProxy.scala
new file mode 100644
index 0000000000..b3f1889c4f
--- /dev/null
+++ b/library/src/main/scala/scala/scalajs/test/EventProxy.scala
@@ -0,0 +1,25 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js API **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.test
+
+import scala.scalajs.js
+
+trait EventProxy extends js.Object {
+ def error(message: js.String, stack: js.Array[js.Dictionary]): Unit = ???
+ def failure(message: js.String, stack: js.Array[js.Dictionary]): Unit = ???
+ def succeeded(message: js.String): Unit = ???
+ def skipped(message: js.String): Unit = ???
+ def pending(message: js.String): Unit = ???
+ def ignored(message: js.String): Unit = ???
+ def canceled(message: js.String): Unit = ???
+
+ def error(message: js.String): Unit = ???
+ def info(message: js.String): Unit = ???
+}
diff --git a/test/src/test/scala/scala/scalajs/test/Jasmine.scala b/library/src/main/scala/scala/scalajs/test/ScriptStackElement.scala
similarity index 50%
rename from test/src/test/scala/scala/scalajs/test/Jasmine.scala
rename to library/src/main/scala/scala/scalajs/test/ScriptStackElement.scala
index c6460d6ed7..d9159a5afb 100644
--- a/test/src/test/scala/scala/scalajs/test/Jasmine.scala
+++ b/library/src/main/scala/scala/scalajs/test/ScriptStackElement.scala
@@ -1,20 +1,22 @@
/* __ *\
-** ________ ___ / / ___ __ ____ Scala.js Test Suite **
+** ________ ___ / / ___ __ ____ Scala.js API **
** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
** /____/\___/_/ |_/____/_/ | |__/ /____/ **
** |/____/ **
\* */
+
+
package scala.scalajs.test
import scala.scalajs.js
-object Jasmine extends js.GlobalScope {
- def describe(name: String, suite: js.Function0[_]): Unit = ???
- def it(title: String, test: js.Function0[_]): Unit = ???
- def xdescribe(name: String, suite: js.Function0[_]): Unit = ???
- def xit(title: String, test: js.Function0[_]): Unit = ???
- def beforeEach(block: js.Function0[_]): Unit = ???
- def afterEach(block: js.Function0[_]): Unit = ???
- def expect(exp: js.Any): JasmineExpectation = ???
+class ScriptStackElement(
+ val fileName: String,
+ val functionName: String,
+ val lineNumber: Int)
+
+object ScriptStackElement {
+ def apply(fileName: String, functionName: String, lineNumber: Int) =
+ new ScriptStackElement(fileName, functionName, lineNumber)
}
diff --git a/library/src/main/scala/scala/scalajs/test/Test.scala b/library/src/main/scala/scala/scalajs/test/Test.scala
new file mode 100644
index 0000000000..5b05086c53
--- /dev/null
+++ b/library/src/main/scala/scala/scalajs/test/Test.scala
@@ -0,0 +1,13 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js API **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.test
+
+/** Marker trait for Scala.js tests */
+trait Test
diff --git a/library/src/main/scala/scala/scalajs/test/TestBridge.scala b/library/src/main/scala/scala/scalajs/test/TestBridge.scala
new file mode 100644
index 0000000000..9f20f8f392
--- /dev/null
+++ b/library/src/main/scala/scala/scalajs/test/TestBridge.scala
@@ -0,0 +1,26 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js API **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.test
+
+import scala.scalajs.js
+import js.Dynamic.{ newInstance, global }
+import js.JavaScriptException
+
+class TestBridge(reporter: EventProxy, framework: String, test: String) {
+ val testOutput = new TestOutputBridge(reporter)
+
+ val testFramework =
+ global.ScalaJS.modules.applyDynamic(framework)()
+ .asInstanceOf[TestFramework]
+
+ testFramework.runTests(testOutput) {
+ global.ScalaJS.modules.applyDynamic(test)()
+ }
+}
diff --git a/library/src/main/scala/scala/scalajs/test/TestFramework.scala b/library/src/main/scala/scala/scalajs/test/TestFramework.scala
new file mode 100644
index 0000000000..9fb59460ac
--- /dev/null
+++ b/library/src/main/scala/scala/scalajs/test/TestFramework.scala
@@ -0,0 +1,14 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js API **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.test
+
+trait TestFramework {
+ def runTests(testOutput: TestOutput)(tests: => Unit): Unit
+}
diff --git a/library/src/main/scala/scala/scalajs/test/TestOutput.scala b/library/src/main/scala/scala/scalajs/test/TestOutput.scala
new file mode 100644
index 0000000000..152ebb846f
--- /dev/null
+++ b/library/src/main/scala/scala/scalajs/test/TestOutput.scala
@@ -0,0 +1,32 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js API **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.test
+
+trait TestOutput {
+ val errorColor: String
+ val successColor: String
+ val infoColor: String
+
+ def color(message: String, color: String): String
+
+ def error(message: String, stack: Array[ScriptStackElement]): Unit
+ def error(message: String): Unit
+ def failure(message: String, stack: Array[ScriptStackElement]): Unit
+ def failure(message: String): Unit
+ def succeeded(message: String): Unit
+ def skipped(message: String): Unit
+ def pending(message: String): Unit
+ def ignored(message: String): Unit
+ def canceled(message: String): Unit
+
+ def log: TestOutputLog
+
+ def getCurrentStack(): Array[ScriptStackElement]
+}
diff --git a/library/src/main/scala/scala/scalajs/test/TestOutputBridge.scala b/library/src/main/scala/scala/scalajs/test/TestOutputBridge.scala
new file mode 100644
index 0000000000..13c0ab132e
--- /dev/null
+++ b/library/src/main/scala/scala/scalajs/test/TestOutputBridge.scala
@@ -0,0 +1,85 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js API **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.test
+
+import scala.scalajs.js
+import js.Dynamic.{ newInstance, global }
+import js.JavaScriptException
+
+class TestOutputBridge(eventProxy: EventProxy) extends TestOutput {
+
+ val errorColor = "\u001b[31m"
+ val successColor = "\u001b[32m"
+ val infoColor = "\u001b[34m"
+ val reset = "\u001b[0m"
+
+ def color(message: String, color: String): String =
+ message.split('\n').mkString(color, reset + '\n' + color, reset)
+
+ def error(message: String, stack: Array[ScriptStackElement]): Unit =
+ eventProxy.error(message, toJsArray(stack))
+
+ def error(message: String): Unit =
+ error(message, Array.empty)
+
+ def failure(message: String, stack: Array[ScriptStackElement]): Unit =
+ eventProxy.failure(message, toJsArray(stack))
+
+ def failure(message: String): Unit =
+ failure(message, Array.empty)
+
+ def succeeded(message: String): Unit =
+ eventProxy.succeeded(message)
+
+ def skipped(message: String): Unit =
+ eventProxy.skipped(message)
+
+ def pending(message: String): Unit =
+ eventProxy.pending(message)
+
+ def ignored(message: String): Unit =
+ eventProxy.ignored(message)
+
+ def canceled(message: String): Unit =
+ eventProxy.canceled(message)
+
+ val log =
+ new TestOutputLog {
+ def info(message: String): Unit = eventProxy.info(message)
+ def error(message: String): Unit = eventProxy.error(message)
+ }
+
+ def getCurrentStack(): Array[ScriptStackElement] = {
+ val RhinoExceptionClass = global.Packages.org.mozilla.javascript.JavaScriptException
+ val rhinoException = newInstance(RhinoExceptionClass)("stack creation")
+ val rhinoStack = rhinoException.getScriptStack().asInstanceOf[js.Array[js.Dynamic]]
+ val stack =
+ for (i <- 0 until rhinoStack.length.asInstanceOf[js.Number].toInt) yield {
+ val e = rhinoStack(i)
+ ScriptStackElement(e.fileName.toString, e.functionName.toString,
+ e.lineNumber.asInstanceOf[js.Number].toInt)
+ }
+
+ stack.toArray
+ }
+
+ private def toJsArray(a: Array[ScriptStackElement]): js.Array[js.Dictionary] =
+ elementToDictionary(a)
+
+ private def elementToDictionary(a: Seq[ScriptStackElement]): Array[js.Dictionary] =
+ (for (el <- a) yield stackElementToDictionary(el)).toArray
+
+ private def stackElementToDictionary(s: ScriptStackElement): js.Dictionary = {
+ js.Dictionary(
+ "fileName" -> (s.fileName: js.String),
+ "functionName" -> (s.functionName: js.String),
+ "lineNumber" -> (s.lineNumber: js.Number))
+ }
+}
diff --git a/library/src/main/scala/scala/scalajs/test/TestOutputLog.scala b/library/src/main/scala/scala/scalajs/test/TestOutputLog.scala
new file mode 100644
index 0000000000..fb0e06ed8c
--- /dev/null
+++ b/library/src/main/scala/scala/scalajs/test/TestOutputLog.scala
@@ -0,0 +1,15 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js API **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.test
+
+trait TestOutputLog {
+ def info(message: String): Unit
+ def error(message: String): Unit
+}
diff --git a/project/ScalaJSBuild.scala b/project/ScalaJSBuild.scala
index f563c8fa1e..11d112cc5c 100644
--- a/project/ScalaJSBuild.scala
+++ b/project/ScalaJSBuild.scala
@@ -62,16 +62,17 @@ object ScalaJSBuild extends Build {
publishArtifact in Compile := false,
clean := clean.dependsOn(
- // compiler, library and sbt-plugin are aggregated
+ // compiler, plugin, library and jasmineTestFramework are aggregated
clean in corejslib, clean in javalib, clean in scalalib,
clean in libraryAux, clean in test, clean in examples,
- clean in exampleHelloWorld, clean in exampleReversi).value,
+ clean in exampleHelloWorld, clean in exampleReversi,
+ clean in exampleTesting).value,
publish := {},
publishLocal := {}
)
).aggregate(
- compiler, plugin, library
+ compiler, plugin, library, jasmineTestFramework
)
lazy val compiler: Project = Project(
@@ -223,6 +224,20 @@ object ScalaJSBuild extends Build {
))
).dependsOn(compiler % "plugin")
+ // Test framework
+
+ lazy val jasmineTestFramework = Project(
+ id = "scalajs-jasmine-test-framework",
+ base = file("jasmine-test-framework"),
+ settings = defaultSettings ++ myScalaJSSettings ++ Seq(
+ name := "Scala.js jasmine test framework",
+
+ libraryDependencies ++= Seq(
+ "org.webjars" % "jasmine" % "1.3.1"
+ )
+ )
+ ).dependsOn(compiler % "plugin", library)
+
// Utils
/* Dirty trick to add our Scala.js library on the classpath without adding a
@@ -230,12 +245,23 @@ object ScalaJSBuild extends Build {
* time we make a change in the compiler, and we want to test it on an
* example or with the test suite.
*/
- def useLibraryButDoNotDependOnIt(config: Configuration) = (
+ def useProjectButDoNotDependOnIt(project: Project, config: Configuration) = (
unmanagedClasspath in config += {
- val libraryJar = (artifactPath in (library, Compile, packageBin)).value
+ val libraryJar = (artifactPath in (project, Compile, packageBin)).value
Attributed.blank(libraryJar)
})
+ def useLibraryButDoNotDependOnIt = Seq(
+ useProjectButDoNotDependOnIt(library, Compile),
+ useProjectButDoNotDependOnIt(library, Test)
+ )
+
+ def useJasmineTestFrameworkButDoNotDependOnIt = Seq(
+ useProjectButDoNotDependOnIt(jasmineTestFramework, Test),
+ libraryDependencies ++=
+ (libraryDependencies in jasmineTestFramework).value map (_ % "test")
+ )
+
// Examples
lazy val examples: Project = Project(
@@ -246,9 +272,10 @@ object ScalaJSBuild extends Build {
)
).aggregate(exampleHelloWorld, exampleReversi)
- lazy val exampleSettings = defaultSettings ++ myScalaJSSettings ++ Seq(
- useLibraryButDoNotDependOnIt(Compile),
-
+ lazy val exampleSettings = defaultSettings ++ myScalaJSSettings ++ (
+ useLibraryButDoNotDependOnIt ++
+ useJasmineTestFrameworkButDoNotDependOnIt
+ ) ++ Seq(
// Add the startup.js file of this example project
unmanagedSources in (Compile, packageJS) +=
baseDirectory.value / "startup.js"
@@ -272,32 +299,31 @@ object ScalaJSBuild extends Build {
)
).dependsOn(compiler % "plugin")
- // Testing
+ lazy val exampleTesting = Project(
+ id = "testing",
+ base = file("examples") / "testing",
+ settings = exampleSettings ++ Seq(
+ name := "Testing - Scala.js example",
+ moduleName := "testing",
- val jasmineVersion = "1.3.1"
+ libraryDependencies ++= Seq(
+ "org.webjars" % "jquery" % "1.10.2" % "test",
+ "org.webjars" % "envjs" % "1.2" % "test"
+ )
+ )
+ ).dependsOn(compiler % "plugin")
+
+ // Testing
lazy val test: Project = Project(
id = "scalajs-test",
base = file("test"),
- settings = defaultSettings ++ myScalaJSSettings ++ Seq(
+ settings = defaultSettings ++ myScalaJSSettings ++ (
+ useLibraryButDoNotDependOnIt ++
+ useJasmineTestFrameworkButDoNotDependOnIt
+ ) ++ Seq(
name := "Scala.js test suite",
- publishArtifact in Compile := false,
- useLibraryButDoNotDependOnIt(Test),
-
- libraryDependencies += "org.webjars" % "jasmine" % jasmineVersion % "test",
-
- // We don't need the HTML reporter, since we use the Rhino reporter
- sources in (Test, packageExternalDepsJS) ~= { srcs =>
- srcs.filterNot(_.name == "jasmine-html.js")
- },
-
- // And a hack to make sure bootstrap.js is included before jasmine.js
- sources in (Test, Keys.test) ~= { srcs =>
- val bootstrap: File = srcs.find(_.name == "bootstrap.js").get
- val (before, after) =
- srcs.filterNot(_ == bootstrap).span(_.name != "jasmine.js")
- before ++ Seq(bootstrap) ++ after
- }
+ publishArtifact in Compile := false
)
).dependsOn(compiler % "plugin")
diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/RhinoBasedRun.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/RhinoBasedRun.scala
deleted file mode 100644
index d13a5737b9..0000000000
--- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/RhinoBasedRun.scala
+++ /dev/null
@@ -1,233 +0,0 @@
-/* Scala.js sbt plugin
- * Copyright 2013 LAMP/EPFL
- * @author Sébastien Doeraene
- */
-
-package scala.scalajs.sbtplugin
-
-import scala.language.implicitConversions
-
-import sbt._
-
-import scala.collection.mutable
-
-import org.mozilla.javascript._
-
-object RhinoBasedRun {
-
- private class ContextOps(val self: Context) {
- def evaluateFile(scope: Scriptable, file: File,
- securityDomain: AnyRef = null): Any = {
- val reader = new java.io.FileReader(file)
- try {
- self.evaluateReader(scope, reader,
- file.getAbsolutePath, 1, securityDomain)
- } finally {
- reader.close()
- }
- }
- }
-
- private implicit def context2ops(ctx: Context): ContextOps =
- new ContextOps(ctx)
-
- /** A proxy for a ScalaJS "scope" field that loads scripts lazily
- *
- * E.g., ScalaJS.c, which is a scope with the Scala.js classes, can be
- * turned to a LazyScalaJSScope. Upon first access to a field of ScalaJS.c,
- * say ScalaJS.c.scala_Option, the script defining that particular
- * field will be loaded.
- * This is possible because the relative path to the script can be derived
- * from the name of the property being accessed.
- *
- * It is immensely useful, because it allows to load lazily only the scripts
- * that are actually needed.
- */
- private class LazyScalaJSScope(
- providers: scala.collection.Map[String, File],
- globalScope: Scriptable,
- base: Scriptable,
- isModule: Boolean = false,
- isTraitImpl: Boolean = false)
- extends Scriptable {
-
- private val fields = mutable.HashMap.empty[String, Any]
- private var prototype: Scriptable = _
- private var parentScope: Scriptable = _
-
- {
- // Pre-fill fields with the properties of `base`
- for (id <- base.getIds()) {
- (id.asInstanceOf[Any]: @unchecked) match {
- case name: String => put(name, this, base.get(name, base))
- case index: Int => put(index, this, base.get(index, base))
- }
- }
- }
-
- private def load(name: String): Unit = {
- val relativeFileName = nameToRelativeFileName(name)
- providers.get(relativeFileName) foreach { file =>
- val ctx = Context.getCurrentContext()
- ctx.evaluateFile(globalScope, file)
- }
- }
-
- private def nameToRelativeFileName(name: String): String = {
- val name1 = if (isTraitImpl) name.split("__")(0) else name
- val name2 = name1.replace("_", "/").replace("$und", "_")
- if (isModule) name2 + "$.js"
- else name2 + ".js"
- }
-
- override def getClassName() = "LazyScalaJSScope"
-
- override def get(name: String, start: Scriptable) = {
- fields.getOrElse(name, {
- load(name)
- fields.getOrElse(name, Scriptable.NOT_FOUND)
- }).asInstanceOf[AnyRef]
- }
- override def get(index: Int, start: Scriptable) =
- get(index.toString, start)
-
- override def has(name: String, start: Scriptable) =
- fields.contains(name)
- override def has(index: Int, start: Scriptable) =
- has(index.toString, start)
-
- override def put(name: String, start: Scriptable, value: Any) = {
- fields(name) = value
- }
- override def put(index: Int, start: Scriptable, value: Any) =
- put(index.toString, start, value)
-
- override def delete(name: String) = ()
- override def delete(index: Int) = ()
-
- override def getPrototype() = prototype
- override def setPrototype(value: Scriptable) = prototype = value
-
- override def getParentScope() = parentScope
- override def setParentScope(value: Scriptable) = parentScope = value
-
- override def getIds() = fields.keys.toArray
-
- override def getDefaultValue(hint: java.lang.Class[_]) = {
- base.getDefaultValue(hint)
- }
-
- override def hasInstance(instance: Scriptable) = false
- }
-
- /** Run a sequence of JavaScript scripts, with a Scala.js flavor
- * If given the original Scala.js classpath, it can hijack the Scala.js
- * scripts in the sequence to load them lazily, the first time they are
- * required.
- */
- def scalaJSRunJavaScript(inputs: Seq[File], trace: (=> Throwable) => Unit,
- console: Option[AnyRef],
- useLazyScalaJSScopes: Boolean = false,
- scalaJSClasspath: Seq[File] = Nil,
- mainClass: Option[String] = None,
- arguments: Array[String] = Array[String]()): Unit = {
- val ctx = Context.enter()
- try {
- val scope = ctx.initStandardObjects()
-
- console foreach { consoleObject =>
- ScriptableObject.putProperty(scope, "console",
- Context.javaToJS(consoleObject, scope))
- }
-
- try {
- if (!useLazyScalaJSScopes ||
- !inputs.exists(_.getName == "scalajs-corejslib.js")) {
- // Easy, just evaluate all input files, in order
- for (input <- inputs)
- ctx.evaluateFile(scope, input)
- } else {
- // The smart thing that hijacks ScalaJS-related things
- runJavaScriptWithLazyScalaJSScopes(ctx, scope, inputs, scalaJSClasspath)
- }
-
- mainClass foreach (runMainClass(ctx, scope, _, arguments))
- } catch {
- case e: Exception =>
- trace(e) // print the stack trace while we're in the Context
- throw new RuntimeException("Exception while running JS code", e)
- }
- } finally {
- Context.exit()
- }
- }
-
- private def runJavaScriptWithLazyScalaJSScopes(ctx: Context,
- scope: Scriptable, inputs: Seq[File],
- scalaJSClasspath: Seq[File]): Unit = {
-
- val providers = mutable.HashMap.empty[String, File]
- val ScalaJSProvider = """[0-9]{4}-(.*\.js)""".r
-
- for (input <- inputs) {
- input.getName match {
- case "scalajs-corejslib.js" =>
- ctx.evaluateFile(scope, input)
-
- // Hijack the global ScalaJS instance
- val ScalaJS = scope.get("ScalaJS", scope).asInstanceOf[Scriptable]
-
- def patchField(name: String, isModule: Boolean = false,
- isTraitImpl: Boolean = false): Unit = {
- val base = ScalaJS.get(name, ScalaJS).asInstanceOf[Scriptable]
- val lazyfied = new LazyScalaJSScope(providers, scope, base,
- isModule, isTraitImpl)
- ScalaJS.put(name, ScalaJS, lazyfied)
- }
-
- patchField("data")
- patchField("c")
- patchField("inheritable")
- patchField("classes")
- patchField("impls", isTraitImpl = true)
- patchField("moduleInstances", isModule = true)
- patchField("modules", isModule = true)
- patchField("is")
- patchField("as")
- patchField("isArrayOf")
- patchField("asArrayOf")
-
- case ScalaJSProvider(fileName) =>
- val relative = scalaJSClasspath collectFirst {
- case base if IO.relativize(base, input).isDefined =>
- IO.relativize(base, input).get
- }
-
- if (relative.isDefined) {
- val rel = relative.get.replace("\\", "/")
- val lastSlash = rel.lastIndexOf("/")
- val relativeFileName = rel.substring(0, lastSlash+1) + fileName
- providers += (relativeFileName -> input)
- } else {
- // Oops! We found a Scala.js file that is not in the classpath!
- ctx.evaluateFile(scope, input)
- }
-
- case _ =>
- ctx.evaluateFile(scope, input)
- }
- }
- }
-
- private def runMainClass(ctx: Context, scope: Scriptable,
- mainClass: String, arguments: Array[String]): Unit = {
-
- val moduleFieldName = mainClass.replace("_", "$und").replace(".", "_")
-
- val script = s"""ScalaJS.modules.$moduleFieldName().main__AT__V(
- ScalaJS.makeNativeArrayWrapper(
- ScalaJS.data.java_lang_String.getArrayOf(), []))"""
-
- ctx.evaluateString(scope, script, "", 1, null)
- }
-}
diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSEnvironment.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSEnvironment.scala
new file mode 100644
index 0000000000..da53c4241c
--- /dev/null
+++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSEnvironment.scala
@@ -0,0 +1,17 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.sbtplugin
+
+import org.mozilla.javascript.Context
+import org.mozilla.javascript.ScriptableObject
+
+trait ScalaJSEnvironment {
+ def runInContextAndScope(code: (Context, ScriptableObject) => Unit): Unit
+}
diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPlugin.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPlugin.scala
index c21262bdea..c3cbd777e8 100644
--- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPlugin.scala
+++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPlugin.scala
@@ -1,18 +1,21 @@
-/* Scala.js sbt plugin
- * Copyright 2013 LAMP/EPFL
- * @author Sébastien Doeraene
- */
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
package scala.scalajs.sbtplugin
import sbt._
-import inc.{ IncOptions, ClassfileManager }
+import sbt.inc.{ IncOptions, ClassfileManager }
import Keys._
import scala.collection.mutable
import SourceMapCat.catJSFilesAndTheirSourceMaps
-import RhinoBasedRun._
import com.google.javascript.jscomp.{
SourceFile => ClosureSource,
@@ -20,9 +23,12 @@ import com.google.javascript.jscomp.{
CompilerOptions => ClosureOptions,
_
}
-import scala.collection.JavaConversions._
+import scala.collection.JavaConverters._
+
+import environment.{Console, LoggerConsole, RhinoBasedScalaJSEnvironment}
+import environment.rhino.{CodeBlock, Utilities}
-import org.mozilla.{ javascript => rhino }
+import scala.scalajs.sbtplugin.testing.TestFramework
object ScalaJSPlugin extends Plugin {
val scalaJSVersion = "0.2-SNAPSHOT"
@@ -44,6 +50,16 @@ object ScalaJSPlugin extends Plugin {
"Pretty-print the output of optimizeJS")
val optimizeJSExterns = taskKey[Seq[File]](
"Extern files to use with optimizeJS")
+
+ val loggingConsole = taskKey[Option[Console]](
+ "The logging console used by the Scala.js jvm environment")
+ val prepareEnvironment = taskKey[ScalaJSEnvironment](
+ "Prepares a jvm environment in where Scala.js files can be run and tested")
+
+ val scalaJSTestBridgeClass = settingKey[String](
+ "The Scala.js class that delegates test calls to the given test framework")
+ val scalaJSTestFramework = settingKey[String](
+ "The Scala.js class that is used as a test framework, for example a class that wraps Jasmine")
}
import ScalaJSKeys._
@@ -88,14 +104,6 @@ object ScalaJSPlugin extends Plugin {
var global = {};
"""
- /** A proxy for a Logger that looks like a Mozilla console object */
- private class LoggingConsole(logger: Logger) {
- def log(x: Any): Unit = logger.info(x.toString)
- def info(x: Any): Unit = logger.info(x.toString)
- def warn(x: Any): Unit = logger.warn(x.toString)
- def error(x: Any): Unit = logger.error(x.toString)
- }
-
val scalaJSExternalCompileConfigSettings: Seq[Setting[_]] = inTask(compile)(
Defaults.runnerTask
) ++ Seq(
@@ -426,7 +434,7 @@ object ScalaJSPlugin extends Plugin {
val compiler = new ClosureCompiler
val result = compiler.compile(
- closureExterns, closureSources, options)
+ closureExterns.asJava, closureSources.asJava, options)
val errors = result.errors.toList
val warnings = result.warnings.toList
@@ -457,15 +465,6 @@ object ScalaJSPlugin extends Plugin {
}
)
- def scalaJSRunJavaScriptTask(streams: TaskKey[TaskStreams],
- sources: TaskKey[Seq[File]], classpath: TaskKey[Classpath]) = Def.task {
- val s = streams.value
- s.log.info("Running ...")
- val console = new LoggingConsole(s.log)
- scalaJSRunJavaScript(sources.value, s.log.trace, Some(console), true,
- classpath.value.map(_.data))
- }
-
def scalaJSRunInputsSettings(scoped: Scoped) = Seq(
sources in scoped := (
(sources in packageExternalDepsJS).value ++
@@ -480,22 +479,65 @@ object ScalaJSPlugin extends Plugin {
)
)
+ def createScalaJSEnvironment(scoped: Scoped): Def.Initialize[Task[ScalaJSEnvironment]] =
+ Def.task {
+ val inputs = (sources in scoped).value
+ val classpath = (fullClasspath in scoped).value.map(_.data)
+ val logger = streams.value.log
+ val console = loggingConsole.value
+
+ new RhinoBasedScalaJSEnvironment(inputs, classpath, console, logger.trace)
+ }
+
val scalaJSRunSettings = scalaJSRunInputsSettings(run) ++ Seq(
- run := {
- scalaJSRunJavaScriptTask(streams, sources in run,
- fullClasspath in run).value
+ prepareEnvironment <<= createScalaJSEnvironment(run),
+
+ run <<= {
+ import Def.parserToInput
+ val parser = Def.spaceDelimited()
+
+ Def.inputTask {
+ val mainClassName = (mainClass in run).value.getOrElse(
+ sys.error("No main class detected."))
+ val args = parser.parsed
+
+ prepareEnvironment.value.runInContextAndScope { (context, scope) =>
+ new CodeBlock(context, scope) with Utilities {
+ val mainModule = getModule(mainClassName)
+ callMethod(mainModule, "main__AT__V", null) // TODO use args
+ }
+ }
+ }
}
)
val scalaJSCompileSettings = scalaJSConfigSettings ++ scalaJSRunSettings
- val scalaJSTestSettings = scalaJSConfigSettings ++ scalaJSRunInputsSettings(test) ++ Seq(
- test := {
- scalaJSRunJavaScriptTask(streams, sources in test,
- fullClasspath in test).value
+ val scalaJSTestFrameworkSettings = Seq(
+ scalaJSTestFramework := "scala.scalajs.test.JasmineTestFramework",
+ scalaJSTestBridgeClass := "scala.scalajs.test.TestBridge",
+ prepareEnvironment <<= createScalaJSEnvironment(test),
+
+ loadedTestFrameworks := {
+ loadedTestFrameworks.value.updated(
+ sbt.TestFramework(classOf[TestFramework].getName),
+ new TestFramework(
+ environment = prepareEnvironment.value,
+ testRunnerClass = scalaJSTestBridgeClass.value,
+ testFramework = scalaJSTestFramework.value)
+ )
}
)
+ val scalaJSTestSettings = (
+ scalaJSConfigSettings ++
+ scalaJSRunInputsSettings(test) ++
+ scalaJSTestFrameworkSettings
+ )
+
+ def defaultLoggingConsole =
+ loggingConsole := Some(new LoggerConsole(streams.value.log))
+
val scalaJSDefaultConfigs = (
inConfig(Compile)(scalaJSCompileSettings) ++
inConfig(Test)(scalaJSTestSettings)
@@ -505,7 +547,9 @@ object ScalaJSPlugin extends Plugin {
excludeDefaultScalaLibrary := false,
optimizeJSPrettyPrint := false,
- optimizeJSExterns := Seq()
+ optimizeJSExterns := Seq(),
+
+ defaultLoggingConsole
)
val scalaJSAbstractSettings: Seq[Setting[_]] = (
diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/Console.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/Console.scala
new file mode 100644
index 0000000000..7493f18f30
--- /dev/null
+++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/Console.scala
@@ -0,0 +1,16 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.sbtplugin.environment
+
+import sbt.Logger
+
+trait Console {
+ def log(msg: Any): Unit
+}
diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/LoggerConsole.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/LoggerConsole.scala
new file mode 100644
index 0000000000..98fa69ecd9
--- /dev/null
+++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/LoggerConsole.scala
@@ -0,0 +1,20 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.sbtplugin.environment
+
+import sbt.Logger
+
+/** A proxy for a Logger that looks like a Mozilla console object */
+class LoggerConsole(logger: Logger) extends Console {
+ def log(msg: Any): Unit = logger.info(msg.toString)
+ def info(msg: Any): Unit = logger.info(msg.toString)
+ def warn(msg: Any): Unit = logger.warn(msg.toString)
+ def error(msg: Any): Unit = logger.error(msg.toString)
+}
diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/RhinoBasedScalaJSEnvironment.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/RhinoBasedScalaJSEnvironment.scala
new file mode 100644
index 0000000000..77508a7017
--- /dev/null
+++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/RhinoBasedScalaJSEnvironment.scala
@@ -0,0 +1,193 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.sbtplugin.environment
+
+import java.io.File
+
+import scala.collection.Map
+import scala.collection.mutable
+import scala.scalajs.sbtplugin.ScalaJSEnvironment
+import scala.scalajs.sbtplugin.sourcemap.SourceMappedException
+import scala.util.matching.Regex
+
+import org.mozilla.javascript.Context
+import org.mozilla.javascript.RhinoException
+import org.mozilla.javascript.Scriptable
+import org.mozilla.javascript.ScriptableObject
+
+import rhino.ContextOps
+import rhino.ScalaJSCoreLib
+import rhino.ScriptableObjectOps
+
+import sbt.IO
+
+class RhinoBasedScalaJSEnvironment(
+ inputs: Seq[File], classpath: Seq[File],
+ console: Option[Console],
+ trace: (=> Throwable) => Unit) extends ScalaJSEnvironment {
+
+ val ScalaJSProviderPattern = """[0-9]{4}-(.*\.js)""".r
+
+ /* Splits the inputs into 4 categories
+ *
+ * 1. The core lib (using lazy loading for Scala.js files on the classpath)
+ * 2. Other js files and
+ * 3. Scala.js files (not on the classpath)
+ * 4. Env.js lib if it is present
+ */
+ lazy val (scalaJSCoreLib, availableJsFiles, scalaJSFiles, envJSLib) = {
+ var scalaJSCoreLib: Option[ScalaJSCoreLib] = None
+ val scalaJSProviders, availableJsFiles = mutable.Map.empty[String, File]
+ val scalaJSFiles = mutable.Seq.empty[File]
+ var envJSLib: Option[File] = None
+
+ inputs.foreach {
+ case ScalaJSCore(lib) =>
+ scalaJSCoreLib = Some(lib.withProviders(scalaJSProviders))
+
+ case file @ RelativeScalaJSProvider(relativeFileName) =>
+ scalaJSProviders += relativeFileName -> file
+
+ case file @ ScalaJSProvider() =>
+ scalaJSFiles :+ file
+
+ case file @ EnvJSLib() =>
+ envJSLib = Some(file)
+
+ case file =>
+ availableJsFiles += file.getName -> file
+ }
+
+ if (scalaJSCoreLib == None && (scalaJSProviders.nonEmpty | scalaJSFiles.nonEmpty))
+ throw new RuntimeException("Could not find scalajs-corejslib.js, make sure it's on the classpath")
+
+ (scalaJSCoreLib, availableJsFiles, scalaJSFiles, envJSLib)
+ }
+
+ /** Executes code in an environment where the Scala.js library is set up to
+ * load its classes lazily.
+ *
+ * Other js files can be loaded using the importScripts javascript
+ * function.
+ */
+ def runInContextAndScope(code: (Context, ScriptableObject) => Unit): Unit = {
+ val context = Context.enter()
+ try {
+ val scope = context.initStandardObjects()
+
+ envJSLib.foreach { file =>
+ context.setOptimizationLevel(-1)
+ //please do not print the envjs header
+ scope.addFunction("print", args => ())
+ context.evaluateFile(scope, file)
+ //add the real print function
+ scope.addFunction("print", print(console))
+ }
+
+ console.foreach { console =>
+ ScriptableObject.putProperty(scope, "console",
+ Context.javaToJS(console, scope))
+ }
+
+ scope.addFunction("importScripts", importScripts(context, scope, availableJsFiles))
+
+ try {
+ // We only need to make the core lib available
+ scalaJSCoreLib.foreach(_.insertInto(context, scope))
+
+ // Any Scala.js files that were not on the classpath need to be loaded
+ scalaJSFiles.foreach(context.evaluateFile(scope, _))
+
+ code(context, scope)
+ } catch {
+ case e: RhinoException =>
+ trace(e) // print the stack trace while we're in the Context
+ throw new SourceMappedException(e)
+
+ case e: Exception =>
+ trace(e) // print the stack trace while we're in the Context
+ throw new RuntimeException("Exception while running JS code", e)
+ }
+ } finally {
+ Context.exit()
+ }
+ }
+
+ private def print(console: Option[Console])(args: Array[AnyRef]) =
+ console.foreach(_.log(args.mkString(" ")))
+
+ private def importScripts(context: Context, globalScope: Scriptable,
+ availableJsFiles: Map[String, File])(args: Array[AnyRef]) = {
+ val urls = args.map(_.toString)
+
+ val files =
+ for {
+ url <- urls
+ possibleName = url.split("/").lastOption
+ name <- possibleName
+ possibleFile = availableJsFiles.get(name)
+ if possibleFile.forall(_.getAbsolutePath endsWith name)
+ } yield url -> possibleFile
+
+ files.foreach {
+ case (_, Some(file)) =>
+ context.evaluateFile(globalScope, file)
+ case (url, None) =>
+ throw new RuntimeException("Could not find file: '" + url +
+ "', make sure you make it available on any classpath")
+ }
+ }
+
+ private class FileNameMatcher(pattern: Regex) {
+ def unapply(file: File): Boolean = {
+ file.getName match {
+ case pattern(_*) => true
+ case _ => false
+ }
+ }
+ }
+
+ private val EnvJSLib = new FileNameMatcher("""env\.rhino\.(?:[\d\.]+\.)?js""".r)
+ private val ScalaJSProvider = new FileNameMatcher(ScalaJSProviderPattern)
+
+ private object ScalaJSCore {
+ def unapply(file: File) =
+ if (file.getName == "scalajs-corejslib.js")
+ Some(new ScalaJSCoreLib(file))
+ else None
+ }
+
+ private object RelativeScalaJSProvider {
+ def unapply(file: File): Option[String] = {
+ val classpath = classpathOf(file)
+
+ (file.getName, classpath) match {
+ case (ScalaJSProviderPattern(fileName), Some(classpath)) =>
+ Some(makeRelative(fileName, classpath))
+ case _ => None
+ }
+ }
+
+ private def makeRelative(fileName: String, classpath: String) = {
+ val rel = classpath.replace("\\", "/")
+ val lastSlash = rel.lastIndexOf("/")
+ val relativeFileName = rel.substring(0, lastSlash + 1) + fileName
+ relativeFileName
+ }
+
+ private def classpathOf(file: File) = {
+ classpath.view
+ .map(IO.relativize(_, file))
+ .collectFirst {
+ case Some(path) => path
+ }
+ }
+ }
+}
diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/CodeBlock.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/CodeBlock.scala
new file mode 100644
index 0000000000..9c8544d386
--- /dev/null
+++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/CodeBlock.scala
@@ -0,0 +1,17 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.sbtplugin.environment.rhino
+
+import org.mozilla.javascript.Scriptable
+import org.mozilla.javascript.Context
+
+abstract class CodeBlock(
+ protected val context: Context,
+ protected val scope: Scriptable)
diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/LazyScalaJSScope.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/LazyScalaJSScope.scala
new file mode 100644
index 0000000000..b85e61b5d3
--- /dev/null
+++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/LazyScalaJSScope.scala
@@ -0,0 +1,103 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.sbtplugin.environment.rhino
+
+import org.mozilla.javascript.Scriptable
+import java.io.File
+import org.mozilla.javascript.Context
+import scala.collection.mutable
+
+/** A proxy for a ScalaJS "scope" field that loads scripts lazily
+ *
+ * E.g., ScalaJS.c, which is a scope with the Scala.js classes, can be
+ * turned to a LazyScalaJSScope. Upon first access to a field of ScalaJS.c,
+ * say ScalaJS.c.scala_Option, the script defining that particular
+ * field will be loaded.
+ * This is possible because the relative path to the script can be derived
+ * from the name of the property being accessed.
+ *
+ * It is immensely useful, because it allows to load lazily only the scripts
+ * that are actually needed.
+ */
+class LazyScalaJSScope(
+ providers: scala.collection.Map[String, File],
+ globalScope: Scriptable,
+ base: Scriptable,
+ isModule: Boolean = false,
+ isTraitImpl: Boolean = false) extends Scriptable {
+
+ private val fields = mutable.HashMap.empty[String, Any]
+ private var prototype: Scriptable = _
+ private var parentScope: Scriptable = _
+
+ {
+ // Pre-fill fields with the properties of `base`
+ for (id <- base.getIds()) {
+ (id.asInstanceOf[Any]: @unchecked) match {
+ case name: String => put(name, this, base.get(name, base))
+ case index: Int => put(index, this, base.get(index, base))
+ }
+ }
+ }
+
+ private def load(name: String): Unit = {
+ val relativeFileName = nameToRelativeFileName(name)
+ providers.get(relativeFileName) foreach { file =>
+ val ctx = Context.getCurrentContext()
+ ctx.evaluateFile(globalScope, file)
+ }
+ }
+
+ private def nameToRelativeFileName(name: String): String = {
+ val name1 = if (isTraitImpl) name.split("__")(0) else name
+ val name2 = name1.replace("_", "/").replace("$und", "_")
+ if (isModule) name2 + "$.js"
+ else name2 + ".js"
+ }
+
+ override def getClassName() = "LazyScalaJSScope"
+
+ override def get(name: String, start: Scriptable) = {
+ fields.getOrElse(name, {
+ load(name)
+ fields.getOrElse(name, Scriptable.NOT_FOUND)
+ }).asInstanceOf[AnyRef]
+ }
+ override def get(index: Int, start: Scriptable) =
+ get(index.toString, start)
+
+ override def has(name: String, start: Scriptable) =
+ fields.contains(name)
+ override def has(index: Int, start: Scriptable) =
+ has(index.toString, start)
+
+ override def put(name: String, start: Scriptable, value: Any) = {
+ fields(name) = value
+ }
+ override def put(index: Int, start: Scriptable, value: Any) =
+ put(index.toString, start, value)
+
+ override def delete(name: String) = ()
+ override def delete(index: Int) = ()
+
+ override def getPrototype() = prototype
+ override def setPrototype(value: Scriptable) = prototype = value
+
+ override def getParentScope() = parentScope
+ override def setParentScope(value: Scriptable) = parentScope = value
+
+ override def getIds() = fields.keys.toArray
+
+ override def getDefaultValue(hint: java.lang.Class[_]) = {
+ base.getDefaultValue(hint)
+ }
+
+ override def hasInstance(instance: Scriptable) = false
+}
diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/ScalaJSCoreLib.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/ScalaJSCoreLib.scala
new file mode 100644
index 0000000000..f86738b01f
--- /dev/null
+++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/ScalaJSCoreLib.scala
@@ -0,0 +1,60 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.sbtplugin.environment.rhino
+
+import org.mozilla.javascript.Scriptable
+import java.io.File
+import org.mozilla.javascript.Context
+import scala.collection.Map
+
+class ScalaJSCoreLib(file: File,
+ scalaJSProviders: Map[String, File] = Map.empty) {
+
+ import ScalaJSCoreLib.Info
+
+ def withProviders(scalaJSProviders: Map[String, File]) =
+ new ScalaJSCoreLib(this.file, scalaJSProviders)
+
+ def insertInto(context: Context, scope: Scriptable) = {
+ context.evaluateFile(scope, file)
+ lazifyScalaJSFields(scope)
+ }
+
+ private val scalaJSLazyFields = Seq(
+ Info("data"),
+ Info("c"),
+ Info("inheritable"),
+ Info("classes"),
+ Info("impls", isTraitImpl = true),
+ Info("moduleInstances", isModule = true),
+ Info("modules", isModule = true),
+ Info("is"),
+ Info("as"),
+ Info("isArrayOf"),
+ Info("asArrayOf"))
+
+ private def lazifyScalaJSFields(scope: Scriptable) = {
+ val ScalaJS = scope.get("ScalaJS", scope).asInstanceOf[Scriptable]
+
+ def makeLazyScalaJSScope(base: Scriptable, isModule: Boolean, isTraitImpl: Boolean) =
+ new LazyScalaJSScope(scalaJSProviders, scope, base, isModule, isTraitImpl)
+
+ for (Info(name, isModule, isTraitImpl) <- scalaJSLazyFields) {
+ val base = ScalaJS.get(name, ScalaJS).asInstanceOf[Scriptable]
+ val lazified = makeLazyScalaJSScope(base, isModule, isTraitImpl)
+ ScalaJS.put(name, ScalaJS, lazified)
+ }
+ }
+}
+
+object ScalaJSCoreLib {
+ private case class Info(name: String,
+ isModule: Boolean = false, isTraitImpl: Boolean = false)
+}
diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/Utilities.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/Utilities.scala
new file mode 100644
index 0000000000..043af65f12
--- /dev/null
+++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/Utilities.scala
@@ -0,0 +1,78 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.sbtplugin.environment.rhino
+
+import scala.annotation.tailrec
+
+import org.mozilla.javascript
+import org.mozilla.javascript.Context
+import org.mozilla.javascript.NativeObject
+import org.mozilla.javascript.Scriptable
+import org.mozilla.javascript.Scriptable.NOT_FOUND
+import org.mozilla.javascript.ScriptableObject
+
+trait Utilities { self: CodeBlock =>
+
+ def getObject(base: String, className: String) = {
+ val classNameKey = className.replaceAll("\\.", "_")
+
+ val classObject =
+ getFrom(scope, s"$base.$classNameKey".split("\\."), scope)
+
+ classObject match {
+ case Right(classObject: javascript.Function) =>
+ classObject
+ case Right(noFunction) =>
+ throw new RuntimeException(
+ s"Could not find constructor function, type of element was: ${noFunction.getClass.getName}")
+ case Left(key) =>
+ throw new RuntimeException(
+ s"Could not find $className, undefined key: $key")
+ }
+ }
+
+ def toArgs(args: Seq[Any]) = args.map(Context.javaToJS(_, scope)).toArray
+
+ def createInstance(className: String, args: Any*) = {
+ val classObject = getObject("ScalaJS.classes", className)
+ classObject.construct(context, scope, toArgs(args))
+ }
+
+ def getModule(className: String) = {
+ val moduleObject = getObject("ScalaJS.modules", className)
+ moduleObject.call(context, scope, scope, Array.empty).asInstanceOf[NativeObject]
+ }
+
+ def callMethod(obj: NativeObject, methodName: String, args: Any*) = {
+ val method = ScriptableObject.getProperty(obj, methodName)
+
+ method match {
+ case NOT_FOUND =>
+ throw new RuntimeException(s"Could not find method $methodName")
+ case method: javascript.Function =>
+ method.call(context, scope, obj, toArgs(args))
+ case other =>
+ throw new RuntimeException(
+ s"$methodName is not a method, type was: ${other.getClass.getName}")
+ }
+ }
+
+ @tailrec
+ private def getFrom(obj: Scriptable, keys: Seq[String],
+ scope: Scriptable): Either[String, Scriptable] = {
+ if (keys.isEmpty) Right(obj)
+ else {
+ val key = keys.head
+ val result = obj.get(key, scope)
+ if (result == NOT_FOUND) Left(key)
+ else getFrom(result.asInstanceOf[Scriptable], keys.tail, scope)
+ }
+ }
+}
diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/package.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/package.scala
new file mode 100644
index 0000000000..0efd6a7891
--- /dev/null
+++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/package.scala
@@ -0,0 +1,49 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.sbtplugin.environment
+
+import java.io.File
+
+import org.mozilla.javascript.Scriptable
+import org.mozilla.javascript.Context
+import org.mozilla.javascript.BaseFunction
+import org.mozilla.javascript.Undefined
+import org.mozilla.javascript.ScriptableObject
+
+package object rhino {
+
+ implicit class ContextOps(val self: Context) extends AnyVal {
+ def evaluateFile(scope: Scriptable, file: File,
+ securityDomain: AnyRef = null): Any = {
+ val reader = new java.io.FileReader(file)
+ try {
+ self.evaluateReader(scope, reader,
+ file.getAbsolutePath, 1, securityDomain)
+ } finally {
+ reader.close()
+ }
+ }
+ }
+
+ implicit class ScriptableObjectOps(val self: ScriptableObject) {
+ def addFunction(name: String, function: Array[AnyRef] => Unit) = {
+ val rhinoFunction =
+ new BaseFunction {
+ override def call(context: Context, scope: Scriptable,
+ thisObj: Scriptable, args: Array[AnyRef]): AnyRef = {
+ function(args)
+ Undefined.instance
+ }
+ }
+
+ ScriptableObject.putProperty(self, name, rhinoFunction)
+ }
+ }
+}
diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/sourcemap/SourceMappedException.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/sourcemap/SourceMappedException.scala
new file mode 100644
index 0000000000..3fd5721210
--- /dev/null
+++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/sourcemap/SourceMappedException.scala
@@ -0,0 +1,36 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.sbtplugin.sourcemap
+
+import org.mozilla.javascript.ScriptStackElement
+import org.mozilla.javascript.RhinoException
+
+case class SourceMappedException(
+ message: String,
+ stack: Array[ScriptStackElement],
+ cause: RhinoException) extends RuntimeException(message, cause) {
+
+ def this(e: RhinoException) =
+ this(e.getMessage, e.getScriptStack, e)
+
+ def this(message: String, stack: Array[ScriptStackElement]) =
+ this(message, stack, null)
+
+ val javaStyleStack =
+ stack.map { el =>
+ new StackTraceElement(
+ "",
+ Option(el.functionName).getOrElse(""),
+ el.fileName,
+ el.lineNumber)
+ }
+
+ setStackTrace(javaStyleStack)
+}
diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/sourcemap/SourceMapper.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/sourcemap/SourceMapper.scala
new file mode 100644
index 0000000000..7203acdbca
--- /dev/null
+++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/sourcemap/SourceMapper.scala
@@ -0,0 +1,65 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.sbtplugin.sourcemap
+
+import org.mozilla.javascript.ScriptStackElement
+import sbt.IO
+import java.io.File
+import com.google.debugging.sourcemap.SourceMapConsumerFactory
+import com.google.debugging.sourcemap.SourceMapSection
+import com.google.debugging.sourcemap.SourceMapConsumerV3
+import com.google.debugging.sourcemap.FilePosition
+
+object SourceMapper {
+ def map(stack: Array[ScriptStackElement]): Array[ScriptStackElement] = {
+ for (el <- stack) yield {
+ val sourceMapFile = new File(el.fileName + ".map")
+ if (sourceMapFile.exists) mapScriptStackElement(el, sourceMapFile)
+ else el
+ }
+ }
+
+ def mapScriptStackElement(el: ScriptStackElement,
+ sourceMapFile: File): ScriptStackElement = {
+ val sourceMapConsumer =
+ SourceMapConsumerFactory
+ .parse(IO.read(sourceMapFile))
+ .asInstanceOf[SourceMapConsumerV3]
+
+ val lineNumber = el.lineNumber
+ val column = getFirstColumn(sourceMapConsumer, lineNumber)
+
+ val originalMapping =
+ sourceMapConsumer.getMappingForLine(lineNumber, column)
+
+ new ScriptStackElement(
+ originalMapping.getOriginalFile,
+ el.functionName,
+ originalMapping.getLineNumber)
+ }
+
+ def getFirstColumn(sourceMapConsumer: SourceMapConsumerV3, lineNumber: Int) = {
+ var column: Option[Int] = None
+
+ sourceMapConsumer.visitMappings(
+ new SourceMapConsumerV3.EntryVisitor {
+ def visit(sourceName: String,
+ symbolName: String,
+ sourceStartPosition: FilePosition,
+ startPosition: FilePosition,
+ endPosition: FilePosition): Unit =
+ // we need to adjust line and column numbers because they are 0 based
+ if (!column.isDefined && startPosition.getLine == lineNumber - 1)
+ column = Some(startPosition.getColumn + 1)
+ })
+
+ column.getOrElse(1)
+ }
+}
diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/EventProxy.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/EventProxy.scala
new file mode 100644
index 0000000000..ae86828af4
--- /dev/null
+++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/EventProxy.scala
@@ -0,0 +1,94 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.sbtplugin.testing
+
+import org.mozilla.javascript.ScriptStackElement
+import org.mozilla.javascript.NativeArray
+import sbt.testing.Logger
+import sbt.testing.EventHandler
+import org.mozilla.javascript.NativeObject
+import scala.scalajs.sbtplugin.sourcemap.SourceMapper
+import scala.scalajs.sbtplugin.sourcemap.SourceMappedException
+
+case class EventProxy(handler: EventHandler, loggers: Array[Logger], events: Events) {
+
+ import events._
+
+ private[testing] def error(message: String, stack: Array[ScriptStackElement]): Unit = {
+ val sourceMappedStack = SourceMapper.map(stack)
+ logWithEvent(_.error,
+ messageWithStack(message, sourceMappedStack),
+ Error(new SourceMappedException(message, sourceMappedStack)))
+ }
+
+ def error(message: String, stack: NativeArray): Unit =
+ error(message, fromNativeArray(stack))
+
+ def failure(message: String, stack: NativeArray): Unit =
+ failure(message, fromNativeArray(stack))
+
+ private[testing] def failure(message: String, stack: Array[ScriptStackElement]) = {
+ logWithEvent(_.error,
+ messageWithStack(message, stack),
+ Failure(new SourceMappedException(message, stack)))
+ }
+
+ def succeeded(message: String): Unit =
+ logWithEvent(_.info, message, Succeeded)
+
+ def skipped(message: String): Unit =
+ logWithEvent(_.info, message, Skipped)
+
+ def pending(message: String): Unit =
+ logWithEvent(_.info, message, Pending)
+
+ def ignored(message: String): Unit =
+ logWithEvent(_.info, message, Ignored)
+
+ def canceled(message: String): Unit =
+ logWithEvent(_.info, message, Canceled)
+
+ def info(message: String): Unit =
+ log(_.info, message)
+
+ def error(message: String): Unit =
+ log(_.error, message)
+
+ private def messageWithStack(message: String, stack: Array[ScriptStackElement]): String =
+ message + stack.mkString("\n", "\n", "")
+
+ private def log(method: Logger => (String => Unit), message: String): Unit = {
+ for (logger <- loggers) {
+ var loggedMessage = message
+ if (!logger.ansiCodesSupported) removeColors(loggedMessage)
+ method(logger)(loggedMessage)
+ }
+ }
+
+ private def logWithEvent(method: Logger => (String => Unit), message: String, event: Event): Unit = {
+ handler handle event
+ log(method, message)
+ }
+
+ private def fromNativeArray(stack: NativeArray) = {
+ stack.toArray.map {
+ case o: NativeObject =>
+ new ScriptStackElement(
+ o.get("fileName").asInstanceOf[String],
+ o.get("functioName").asInstanceOf[String],
+ o.get("lineNumber").asInstanceOf[Double].toInt)
+ }
+ }
+
+ private val colorPattern = raw"\033\[\d{1, 2}m"
+
+ private def removeColors(message: String): String =
+ message.replaceAll(colorPattern, "")
+}
diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/Events.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/Events.scala
new file mode 100644
index 0000000000..7d7fc82e29
--- /dev/null
+++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/Events.scala
@@ -0,0 +1,39 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.sbtplugin.testing
+
+import sbt.testing.TaskDef
+import sbt.testing.OptionalThrowable
+import sbt.testing.{ Event => SbtEvent }
+import sbt.testing.Status
+import sbt.testing.SuiteSelector
+
+class Events(taskDef: TaskDef) {
+
+ abstract class Event(val status: Status,
+ val throwable: OptionalThrowable = new OptionalThrowable) extends SbtEvent {
+ val fullyQualifiedName = taskDef.fullyQualifiedName
+ val fingerprint = taskDef.fingerprint
+ val selector = taskDef.selectors.headOption.getOrElse(new SuiteSelector)
+ val duration = -1L
+ }
+
+ case class Error(exception: Throwable) extends Event(
+ Status.Error, new OptionalThrowable(exception))
+
+ case class Failure(exception: Throwable) extends Event(
+ Status.Failure, new OptionalThrowable(exception))
+
+ case object Succeeded extends Event(Status.Success)
+ case object Skipped extends Event(Status.Skipped)
+ case object Pending extends Event(Status.Pending)
+ case object Ignored extends Event(Status.Ignored)
+ case object Canceled extends Event(Status.Canceled)
+}
diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestFramework.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestFramework.scala
new file mode 100644
index 0000000000..4867db4ad0
--- /dev/null
+++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestFramework.scala
@@ -0,0 +1,34 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.sbtplugin.testing
+
+import scala.scalajs.sbtplugin.ScalaJSEnvironment
+import sbt.testing.Fingerprint
+import sbt.testing.Framework
+import sbt.testing.SubclassFingerprint
+import sbt.testing.Runner
+
+class TestFramework(environment: ScalaJSEnvironment, testRunnerClass: String,
+ testFramework: String) extends Framework {
+
+ val name = "Scala.js Test Framework"
+
+ lazy val fingerprints = Array[Fingerprint](f1)
+
+ val f1 = new SubclassFingerprint {
+ val isModule = true
+ val superclassName = "scala.scalajs.test.Test"
+ val requireNoArgConstructor = true
+ }
+
+ def runner(args: Array[String], remoteArgs: Array[String],
+ testClassLoader: ClassLoader): Runner =
+ TestRunner(args, remoteArgs, environment, testRunnerClass, testFramework)
+}
diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestRunner.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestRunner.scala
new file mode 100644
index 0000000000..66d8a6c3c4
--- /dev/null
+++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestRunner.scala
@@ -0,0 +1,32 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.sbtplugin.testing
+
+import sbt.testing.TaskDef
+import sbt.testing.Task
+import sbt.testing.Runner
+import scala.scalajs.sbtplugin.ScalaJSEnvironment
+
+case class TestRunner(
+ args: Array[String], remoteArgs: Array[String],
+ environment: ScalaJSEnvironment,
+ testRunnerClass: String, testFramework: String) extends Runner {
+
+ def tasks(taskDefs: Array[TaskDef]): Array[Task] =
+ if (_done) throw new IllegalStateException("Done has already been called")
+ else taskDefs.map(TestTask(environment, testRunnerClass, testFramework))
+
+ def done(): String = {
+ _done = true
+ ""
+ }
+
+ private var _done = false
+}
diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestTask.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestTask.scala
new file mode 100644
index 0000000000..192461c5b0
--- /dev/null
+++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestTask.scala
@@ -0,0 +1,52 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.sbtplugin.testing
+
+import sbt.testing.TaskDef
+import sbt.testing.EventHandler
+import sbt.testing.Task
+import sbt.testing.Logger
+import scala.scalajs.sbtplugin.ScalaJSEnvironment
+import scala.annotation.tailrec
+import org.mozilla.javascript.Context
+import org.mozilla.javascript.Scriptable
+import org.mozilla.javascript
+import org.mozilla.javascript.Scriptable.NOT_FOUND
+import org.mozilla.javascript.RhinoException
+import scala.scalajs.sbtplugin.environment.rhino.CodeBlock
+import scala.scalajs.sbtplugin.environment.rhino.Utilities
+
+case class TestTask(
+ environment: ScalaJSEnvironment,
+ testRunnerClass: String,
+ testFramework: String)(val taskDef: TaskDef) extends Task {
+
+ val tags = Array.empty[String]
+
+ def execute(eventHandler: EventHandler, loggers: Array[Logger]): Array[Task] = {
+ val testKey = taskDef.fullyQualifiedName.replaceAll("\\.", "_")
+ val testFrameworkKey = testFramework.replaceAll("\\.", "_")
+
+ val eventProxy = EventProxy(eventHandler, loggers, new Events(taskDef))
+
+ environment.runInContextAndScope { (context, scope) =>
+ new CodeBlock(context, scope) with Utilities {
+ try {
+ createInstance(testRunnerClass, eventProxy, testFrameworkKey, testKey)
+ } catch {
+ case t: RhinoException =>
+ eventProxy.error(t.details, t.getScriptStack())
+ }
+ }
+ }
+
+ Array.empty
+ }
+}
diff --git a/test/src/test/resources/bootstrap.js b/test/src/test/resources/bootstrap.js
deleted file mode 100644
index 981b5f95d7..0000000000
--- a/test/src/test/resources/bootstrap.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/* __ *\
-** ________ ___ / / ___ __ ____ Scala.js Test Suite **
-** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
-** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
-** /____/\___/_/ |_/____/_/ | |__/ /____/ **
-** |/____/ **
-\* */
-
-/**
- * This file is loaded first and used for patching the JS environment.
- */
-
-function scalaJSStub(name) {
- return function() {
- console.log("Stub for " + name + " called");
- }
-};
-
-/* Stub-out timer methods used by Jasmine and not provided by Rhino. */
-if (typeof setTimeout == 'undefined') {
- var setTimeout = scalaJSStub('setTimeout');
- var clearTimeout = scalaJSStub('clearTimeout');
- var setInterval = scalaJSStub('setInterval');
- var clearInterval = scalaJSStub('clearInterval');
-}
diff --git a/test/src/test/resources/jasmine_rhino_reporter.js b/test/src/test/resources/jasmine_rhino_reporter.js
deleted file mode 100644
index 4b0943a6dd..0000000000
--- a/test/src/test/resources/jasmine_rhino_reporter.js
+++ /dev/null
@@ -1,142 +0,0 @@
-/* __ *\
-** ________ ___ / / ___ __ ____ Scala.js Test Suite **
-** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
-** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
-** /____/\___/_/ |_/____/_/ | |__/ /____/ **
-** |/____/ **
-\* */
-
-/**
- * Console-oriented reporter for Rhino. Colorizes output based on the value of
- * the TERM env variable.
- *
- * Usage:
- *
- * jasmine.getEnv().addReporter(new jasmine.RhinoReporter());
- * jasmine.getEnv().execute();
- */
-
-(function() {
- if (!jasmine) {
- throw new Exception("jasmine library does not exist in global namespace!");
- }
-
- var RhinoReporter = function() {
- };
-
- RhinoReporter.prototype = {
- reportRunnerResults : function(runner) {
- var out = this.failed ? failed : passed
- out("%.0f spec(s), %.0f failure(s) in %.3fs.", this.executed,
- this.failed, (now() - this.started) / 1000);
- },
-
- reportRunnerStarting : function(runner) {
- this.started = now();
- this.executed = 0;
- this.passed = 0;
- this.failed = 0;
- },
-
- reportSpecResults : function(spec) {
- var results = spec.results();
- if (results.passed()) {
- passed(" %s", spec.description);
- } else {
- failed(" %s", spec.description);
-
- for (var i = 0; i < results.getItems().length; i++) {
- var result = results.getItems()[i];
-
- if (result.type == 'log') {
- print(" %s", result.toString());
- } else if (result.type == 'expect') {
- if (result.passed && result.passed()) {
- print(" ✓ %s", result.message);
- } else {
- print(" ☹ %s", result.message);
- if (result.trace.stack) {
- print("%s", result.trace.stack);
- }
- }
- }
- }
- }
- },
-
- reportSpecStarting : function(spec) {
- if (this.suite != spec.suite) {
- this.suite = spec.suite;
- suite(this.suite.description);
- }
- },
-
- reportSuiteResults : function(suite) {
- var results = suite.results();
- if (results.passedCount != results.totalCount)
- failed("%.0f of %.0f failed", results.failedCount, results.totalCount);
- this.passed += results.passedCount;
- this.failed += results.failedCount;
- this.executed += results.totalCount;
- print();
- },
-
- log : function(str) {
- print("%s", str);
- }
- };
-
- function now() {
- return (new Date()).getTime();
- }
-
- function suite(str) {
- print("%s", format(arguments));
- }
-
- function passed(str) {
- print("%s%s%s", Color.GREEN, format(arguments), Color.RESET);
- }
-
- function failed(str) {
- print("%s%s%s", Color.RED, format(arguments), Color.RESET);
- }
-
- function print() {
- var msg = arguments.length > 0 ? format(arguments) : "";
- console.log(msg);
- }
-
- function format(args) {
- return java.lang.String.format(args[0], args[1], args[2], args[3], args[4],
- args[5], args[6], args[7], args[8], args[9], args[10]);
- }
-
- var Color = {
- RESET : "\033[m",
- GREEN : "\033[32m",
- RED : "\033[31m",
- BLUE : "\033[34m"
- };
-
- var ColorTerminals = [ 'xterm' ];
-
- function isColorTerm() {
- var term = java.lang.System.getenv("TERM");
-
- for (var i in ColorTerminals) {
- if (term == ColorTerminals[i]) {
- return true;
- }
- }
- return false;
- }
-
- if (!isColorTerm()) {
- for (var i in Color) {
- Color[i] = '';
- }
- }
-
- jasmine.RhinoReporter = RhinoReporter;
-})();
diff --git a/test/src/test/resources/main.js b/test/src/test/resources/main.js
deleted file mode 100644
index e12a0b4b05..0000000000
--- a/test/src/test/resources/main.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/* __ *\
-** ________ ___ / / ___ __ ____ Scala.js Test Suite **
-** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
-** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
-** /____/\___/_/ |_/____/_/ | |__/ /____/ **
-** |/____/ **
-\* */
-
-console.log("Starting Scala.js test suite");
-console.log("");
-
-/* Load all test suites ... */
-ScalaJS.modules.scala_scalajs_test_compiler_InteroperabilityTest();
-ScalaJS.modules.scala_scalajs_test_compiler_RegressionTest();
-ScalaJS.modules.scala_scalajs_test_compiler_LongTest();
-
-ScalaJS.modules.scala_scalajs_test_javalib_ObjectTest();
-ScalaJS.modules.scala_scalajs_test_javalib_IntegerTest();
-ScalaJS.modules.scala_scalajs_test_javalib_StringTest();
-ScalaJS.modules.scala_scalajs_test_javalib_ArraysTest();
-ScalaJS.modules.scala_scalajs_test_javalib_LongTest();
-ScalaJS.modules.scala_scalajs_test_javalib_ThrowablesTest();
-ScalaJS.modules.scala_scalajs_test_javalib_FormatterTest();
-
-ScalaJS.modules.scala_scalajs_test_scalalib_EnumerationTest();
-
-ScalaJS.modules.scala_scalajs_test_jsinterop_ArrayTest();
-ScalaJS.modules.scala_scalajs_test_jsinterop_DictionaryTest();
-ScalaJS.modules.scala_scalajs_test_jsinterop_DynamicTest();
-ScalaJS.modules.scala_scalajs_test_jsinterop_RuntimeLongTest();
-
-/* ... and run them. */
-var jasmineEnv = jasmine.getEnv();
-jasmineEnv.addReporter(new jasmine.RhinoReporter());
-jasmineEnv.updateInterval = 0;
-jasmineEnv.execute();
-
-if (jasmineEnv.currentRunner().results().failedCount > 0)
- throw new Error("Some tests failed")
diff --git a/test/src/test/scala/scala/scalajs/test/compiler/InteroperabilityTest.scala b/test/src/test/scala/scala/scalajs/test/compiler/InteroperabilityTest.scala
index 8f6a05edfc..e1b88b40f3 100644
--- a/test/src/test/scala/scala/scalajs/test/compiler/InteroperabilityTest.scala
+++ b/test/src/test/scala/scala/scalajs/test/compiler/InteroperabilityTest.scala
@@ -9,7 +9,7 @@ package scala.scalajs.test
package compiler
import scala.scalajs.js
-import scala.scalajs.test.ScalaJSTest
+import scala.scalajs.test.JasmineTest
import scala.scalajs.js.annotation.JSName
import scala.scalajs.js.annotation.JSBracketAccess
@@ -17,7 +17,7 @@ import scala.scalajs.js.annotation.JSBracketAccess
* Based on examples in:
* http://lampwww.epfl.ch/~doeraene/scala-js/doc/js-interoperability.html
*/
-object InteroperabilityTest extends ScalaJSTest {
+object InteroperabilityTest extends JasmineTest {
describe("JavaScript interoperability") {
diff --git a/test/src/test/scala/scala/scalajs/test/compiler/LongTest.scala b/test/src/test/scala/scala/scalajs/test/compiler/LongTest.scala
index fb6b7af895..c61094cb9d 100644
--- a/test/src/test/scala/scala/scalajs/test/compiler/LongTest.scala
+++ b/test/src/test/scala/scala/scalajs/test/compiler/LongTest.scala
@@ -8,7 +8,6 @@
package scala.scalajs.test
package compiler
-import scala.scalajs.test.ScalaJSTest
import scala.scalajs.js
/**
@@ -17,7 +16,7 @@ import scala.scalajs.js
* see scala.scalajs.test.jsinterop.RuntimeLongTest
* for a test of the implementation itself
*/
-object LongTest extends ScalaJSTest {
+object LongTest extends JasmineTest {
describe("JavaScript 64-bit long compatibility") {
it("should correctly handle literals") {
diff --git a/test/src/test/scala/scala/scalajs/test/compiler/RegressionTest.scala b/test/src/test/scala/scala/scalajs/test/compiler/RegressionTest.scala
index a6010a96df..a3dbe38e97 100644
--- a/test/src/test/scala/scala/scalajs/test/compiler/RegressionTest.scala
+++ b/test/src/test/scala/scala/scalajs/test/compiler/RegressionTest.scala
@@ -9,9 +9,9 @@ package scala.scalajs.test
package compiler
import scala.scalajs.js
-import scala.scalajs.test.ScalaJSTest
+import scala.scalajs.test.JasmineTest
-object RegressionTest extends ScalaJSTest {
+object RegressionTest extends JasmineTest {
describe("Scala.js compiler regression tests") {
diff --git a/test/src/test/scala/scala/scalajs/test/javalib/ArraysTest.scala b/test/src/test/scala/scala/scalajs/test/javalib/ArraysTest.scala
index 7cf62efcfc..dd17dcd18a 100644
--- a/test/src/test/scala/scala/scalajs/test/javalib/ArraysTest.scala
+++ b/test/src/test/scala/scala/scalajs/test/javalib/ArraysTest.scala
@@ -9,11 +9,10 @@ package scala.scalajs.test
package javalib
import scala.scalajs.js
-import scala.scalajs.test.ScalaJSTest
-
+import scala.scalajs.test.JasmineTest
import java.util.{ Arrays, Comparator }
-object ArraysTest extends ScalaJSTest {
+object ArraysTest extends JasmineTest {
val stringComparator = new Comparator[String]() {
def compare(s1: String, s2: String) = s1.compareTo(s2)
diff --git a/test/src/test/scala/scala/scalajs/test/javalib/FormatterTest.scala b/test/src/test/scala/scala/scalajs/test/javalib/FormatterTest.scala
index 3ead31fc03..ca3c14a065 100644
--- a/test/src/test/scala/scala/scalajs/test/javalib/FormatterTest.scala
+++ b/test/src/test/scala/scala/scalajs/test/javalib/FormatterTest.scala
@@ -9,12 +9,12 @@ package scala.scalajs.test
package javalib
import scala.scalajs.js
-import scala.scalajs.test.ScalaJSTest
+import scala.scalajs.test.JasmineTest
import java.util.{ Formatter, Formattable, FormattableFlags }
import java.lang.{
- Double => JDouble,
+ Double => JDouble,
Float => JFloat,
Integer => JInteger,
Long => JLong,
@@ -22,9 +22,9 @@ import java.lang.{
Boolean => JBoolean,
String => JString
}
-
-object FormatterTest extends ScalaJSTest {
+
+object FormatterTest extends JasmineTest {
class HelperClass
class FormattableClass extends Formattable {
diff --git a/test/src/test/scala/scala/scalajs/test/javalib/IntegerTest.scala b/test/src/test/scala/scala/scalajs/test/javalib/IntegerTest.scala
index b2d45a5fc2..b50022097c 100644
--- a/test/src/test/scala/scala/scalajs/test/javalib/IntegerTest.scala
+++ b/test/src/test/scala/scala/scalajs/test/javalib/IntegerTest.scala
@@ -8,10 +8,10 @@
package scala.scalajs.test
package javalib
-import scala.scalajs.test.ScalaJSTest
+import scala.scalajs.test.JasmineTest
import scala.scalajs.js.Any.fromInt
-object IntegerTest extends ScalaJSTest {
+object IntegerTest extends JasmineTest {
describe("java.lang.Integer") {
diff --git a/test/src/test/scala/scala/scalajs/test/javalib/LongTest.scala b/test/src/test/scala/scala/scalajs/test/javalib/LongTest.scala
index 7a0049a357..04f3c277b4 100644
--- a/test/src/test/scala/scala/scalajs/test/javalib/LongTest.scala
+++ b/test/src/test/scala/scala/scalajs/test/javalib/LongTest.scala
@@ -8,14 +8,13 @@
package scala.scalajs.test
package javalib
-import scala.scalajs.test.ScalaJSTest
import java.lang.Long
/**
* tests the implementation of the java standard library Long
* requires jsinterop/LongTest to work to make sense
*/
-object LongTest extends ScalaJSTest {
+object LongTest extends JasmineTest {
describe("java.lang.Long") {
it("should implement bitCount") {
diff --git a/test/src/test/scala/scala/scalajs/test/javalib/ObjectTest.scala b/test/src/test/scala/scala/scalajs/test/javalib/ObjectTest.scala
index 9c24528e71..d9cc187db6 100644
--- a/test/src/test/scala/scala/scalajs/test/javalib/ObjectTest.scala
+++ b/test/src/test/scala/scala/scalajs/test/javalib/ObjectTest.scala
@@ -8,9 +8,9 @@
package scala.scalajs.test
package javalib
-import scala.scalajs.test.ScalaJSTest
+import scala.scalajs.test.JasmineTest
-object ObjectTest extends ScalaJSTest {
+object ObjectTest extends JasmineTest {
describe("java.lang.Object") {
diff --git a/test/src/test/scala/scala/scalajs/test/javalib/StringTest.scala b/test/src/test/scala/scala/scalajs/test/javalib/StringTest.scala
index c42f4c73f0..9ba4801e12 100644
--- a/test/src/test/scala/scala/scalajs/test/javalib/StringTest.scala
+++ b/test/src/test/scala/scala/scalajs/test/javalib/StringTest.scala
@@ -9,9 +9,9 @@ package scala.scalajs.test
package javalib
import scala.scalajs.js
-import scala.scalajs.test.ScalaJSTest
+import scala.scalajs.test.JasmineTest
-object StringTest extends ScalaJSTest {
+object StringTest extends JasmineTest {
describe("java.lang.String") {
diff --git a/test/src/test/scala/scala/scalajs/test/javalib/ThrowablesTest.scala b/test/src/test/scala/scala/scalajs/test/javalib/ThrowablesTest.scala
index 1f10098c4f..93aac0b214 100644
--- a/test/src/test/scala/scala/scalajs/test/javalib/ThrowablesTest.scala
+++ b/test/src/test/scala/scala/scalajs/test/javalib/ThrowablesTest.scala
@@ -8,9 +8,7 @@
package scala.scalajs.test
package javalib
-import scala.scalajs.test.ScalaJSTest
-
-object ThrowablesTest extends ScalaJSTest {
+object ThrowablesTest extends JasmineTest {
describe("java.lang.Throwables, java.util.Throwables") {
it("should define all java.lang and java.util Errors/Exceptions") {
@@ -69,10 +67,10 @@ object ThrowablesTest extends ScalaJSTest {
new ConcurrentModificationException()
new DuplicateFormatFlagsException("")
new EmptyStackException()
- new FormatFlagsConversionMismatchException("", '\0')
+ new FormatFlagsConversionMismatchException("", '\u0000')
new FormatterClosedException()
new IllegalFormatCodePointException(0)
- new IllegalFormatConversionException('\0', new Object().getClass)
+ new IllegalFormatConversionException('\u0000', new Object().getClass)
new IllegalFormatFlagsException("")
new IllegalFormatPrecisionException(0)
new IllegalFormatWidthException(0)
diff --git a/test/src/test/scala/scala/scalajs/test/jsinterop/ArrayTest.scala b/test/src/test/scala/scala/scalajs/test/jsinterop/ArrayTest.scala
index 7258257a93..b5a40ef927 100644
--- a/test/src/test/scala/scala/scalajs/test/jsinterop/ArrayTest.scala
+++ b/test/src/test/scala/scala/scalajs/test/jsinterop/ArrayTest.scala
@@ -9,9 +9,9 @@ package scala.scalajs.test
package jsinterop
import scala.scalajs.js
-import scala.scalajs.test.ScalaJSTest
+import scala.scalajs.test.JasmineTest
-object ArrayTest extends ScalaJSTest {
+object ArrayTest extends JasmineTest {
describe("scala.scalajs.js.Array") {
diff --git a/test/src/test/scala/scala/scalajs/test/jsinterop/DictionaryTest.scala b/test/src/test/scala/scala/scalajs/test/jsinterop/DictionaryTest.scala
index d1df5f865a..0887ac2276 100644
--- a/test/src/test/scala/scala/scalajs/test/jsinterop/DictionaryTest.scala
+++ b/test/src/test/scala/scala/scalajs/test/jsinterop/DictionaryTest.scala
@@ -9,9 +9,9 @@ package scala.scalajs.test
package jsinterop
import scala.scalajs.js
-import scala.scalajs.test.ScalaJSTest
+import scala.scalajs.test.JasmineTest
-object DictionaryTest extends ScalaJSTest {
+object DictionaryTest extends JasmineTest {
describe("scala.scalajs.js.Dictionary") {
diff --git a/test/src/test/scala/scala/scalajs/test/jsinterop/DynamicTest.scala b/test/src/test/scala/scala/scalajs/test/jsinterop/DynamicTest.scala
index a5e10d9e58..9e7ddd6c5e 100644
--- a/test/src/test/scala/scala/scalajs/test/jsinterop/DynamicTest.scala
+++ b/test/src/test/scala/scala/scalajs/test/jsinterop/DynamicTest.scala
@@ -9,9 +9,9 @@ package scala.scalajs.test
package jsinterop
import scala.scalajs.js
-import scala.scalajs.test.ScalaJSTest
+import scala.scalajs.test.JasmineTest
-object DynamicTest extends ScalaJSTest {
+object DynamicTest extends JasmineTest {
describe("scala.scalajs.js.Dynamic") {
diff --git a/test/src/test/scala/scala/scalajs/test/jsinterop/RuntimeLongTest.scala b/test/src/test/scala/scala/scalajs/test/jsinterop/RuntimeLongTest.scala
index 1164b61be2..0c288b9cb7 100644
--- a/test/src/test/scala/scala/scalajs/test/jsinterop/RuntimeLongTest.scala
+++ b/test/src/test/scala/scala/scalajs/test/jsinterop/RuntimeLongTest.scala
@@ -8,14 +8,14 @@
package scala.scalajs.test
package jsinterop
-import scala.scalajs.test.ScalaJSTest
import scala.scalajs.runtime.Long
+import org.scalajs.jasmine.JasmineExpectation
/**
* test the runtime Long implementation directly
* does not depend on magic compiler Long rewriting
*/
-object RuntimeLongTest extends ScalaJSTest {
+object RuntimeLongTest extends JasmineTest {
/** overload expect for long to add toString */
def expect(l: Long): JasmineExpectation = expect(l.toHexString)
@@ -52,7 +52,7 @@ object RuntimeLongTest extends ScalaJSTest {
expect(Long.fromInt(7) * Long.fromInt(15)).toEqual("0000000000000069")
expect(Long.fromInt(-7) * Long.fromInt(15)).toEqual("ffffffffffffff97")
expect(maxInt * maxInt).toEqual( "3fffffff00000001")
- expect(Long.fromHexString("001000000000000e") *
+ expect(Long.fromHexString("001000000000000e") *
Long.fromInt(-4)).toEqual("ffbfffffffffffc8")
}
@@ -91,7 +91,7 @@ object RuntimeLongTest extends ScalaJSTest {
expect(Long.fromInt(5).toDouble).toEqual(5.0)
expect((maxInt+one).toDouble).toEqual(2147483648.0)
}
-
+
it("should correctly implement fromString") {
expect(Long.fromString("4")).toEqual( "0000000000000004")
expect(Long.fromString("-4")).toEqual( "fffffffffffffffc")
diff --git a/test/src/test/scala/scala/scalajs/test/scalalib/EnumerationTest.scala b/test/src/test/scala/scala/scalajs/test/scalalib/EnumerationTest.scala
index 884eaa8fd5..80e214588f 100644
--- a/test/src/test/scala/scala/scalajs/test/scalalib/EnumerationTest.scala
+++ b/test/src/test/scala/scala/scalajs/test/scalalib/EnumerationTest.scala
@@ -8,9 +8,9 @@
package scala.scalajs.test
package scalalib
-import scala.scalajs.test.ScalaJSTest
+import scala.scalajs.test.JasmineTest
-object EnumerationTest extends ScalaJSTest {
+object EnumerationTest extends JasmineTest {
describe("scala.Enumeration") {