diff options
Diffstat (limited to 'jasmine-test-framework/src/main/scala/org/scalajs/jasminetest')
4 files changed, 312 insertions, 0 deletions
diff --git a/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/JasmineTest.scala b/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/JasmineTest.scala new file mode 100644 index 0000000..715d39d --- /dev/null +++ b/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/JasmineTest.scala @@ -0,0 +1,34 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Framework ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package org.scalajs.jasminetest + +import scala.scalajs.js +import scala.scalajs.testbridge._ + +import java.util.regex.Pattern +import org.scalajs.jasmine.Jasmine +import org.scalajs.jasmine.JasmineExpectation + +class JasmineTest extends Test with TestSuiteContext { + def jasmine = Jasmine.jasmine + 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 _) + def xit(title: String)(test: => Unit): Unit = Jasmine.xit(title, test _) + def beforeEach(block: => Unit): Unit = Jasmine.beforeEach(block _) + def afterEach(block: => Unit): Unit = Jasmine.afterEach(block _) + def expect(exp: CharSequence): JasmineExpectation = + Jasmine.expect(if (exp == null) null else exp.toString) + def expect(exp: js.Any): JasmineExpectation = Jasmine.expect(exp) + def runs(block: => Unit): Unit = Jasmine.runs(block _) + def waits(timeout: Int): Unit = Jasmine.waits(timeout) + def waitsFor(block: => Boolean, errorMsg: String, timeout: Int): Unit = + Jasmine.waitsFor(block _, errorMsg, timeout) +} diff --git a/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/JasmineTestFramework.scala b/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/JasmineTestFramework.scala new file mode 100644 index 0000000..2686e31 --- /dev/null +++ b/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/JasmineTestFramework.scala @@ -0,0 +1,93 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Framework ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package org.scalajs.jasminetest + +import scala.scalajs.js +import scala.scalajs.js.Dynamic.global +import scala.scalajs.js.JavaScriptException +import scala.scalajs.js.annotation.JSExport + +import scala.scalajs.testbridge._ + +import scala.collection.mutable + +object JasmineTestFramework extends TestFramework { + createStackPropertyOnThrowable() + + private def createStackPropertyOnThrowable(): Unit = { + /* All Jasmine cares about when looking for stack trace data is a field + * `stack` on the error object. Our Throwables do not have a `stack` field + * because they are not subclasses of the JavaScript class Error. + * However, a genuine Error object with the proper (lazy) stack field is + * stored under the property stackdata by StackTrace. + * This code installs a property getter on Throwable that will redirect + * `throwable.stack` to `throwable.stackdata.stack` (when it exists). + */ + + val ThrowablePrototype = js.Object.getPrototypeOf( + (new Throwable).asInstanceOf[js.Object]).asInstanceOf[js.Object] + + js.Object.defineProperty(ThrowablePrototype, "stack", js.Dynamic.literal( + configurable = false, + enumerable = false, + get = { (self: js.Dynamic) => + self.stackdata && self.stackdata.stack + }: js.ThisFunction + ).asInstanceOf[js.PropertyDescriptor]) + } + + private val tags = mutable.Set.empty[String] + + def hasTag(tag: String): Boolean = tags.contains(tag) + + def runTest(testOutput: TestOutput, args: js.Array[String])( + test: js.Function0[Test]): Unit = { + + val jasmine = global.jasmine + val reporter = new JasmineTestReporter(testOutput) + + handleArgs(args, testOutput) + + try { + test() + + val jasmineEnv = jasmine.getEnv() + jasmineEnv.addReporter(reporter.asInstanceOf[js.Any]) + jasmineEnv.updateInterval = 0 + jasmineEnv.execute() + } catch { + case throwable@JavaScriptException(exception) => + testOutput.error("Problem executing code in tests: " + exception, + throwable.getStackTrace()) + } finally { + clearTags() + } + } + + /** Set tags used in tests. Only use when invoking with HTML reporter */ + @JSExport + def setTags(newTags: String*): Unit = { + clearTags() + tags ++= newTags + } + + /** Clear tags used in tests. Only use when invoking with HTML reporter */ + @JSExport + def clearTags(): Unit = tags.clear() + + private def handleArgs(args: js.Array[String], testOutput: TestOutput) = { + for (arg <- args) { + if (arg.startsWith("-t")) + tags += arg.stripPrefix("-t") + else + testOutput.log.warn(s"Jasmine: Discarding argument: $arg") + } + } +} diff --git a/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/JasmineTestReporter.scala b/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/JasmineTestReporter.scala new file mode 100644 index 0000000..79a7c75 --- /dev/null +++ b/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/JasmineTestReporter.scala @@ -0,0 +1,130 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Framework ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package org.scalajs.jasminetest + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSExport + +import scala.scalajs.runtime.StackTrace + +import scala.scalajs.testbridge._ + +import org.scalajs.jasmine.ExpectationResult +import org.scalajs.jasmine.Result +import org.scalajs.jasmine.Spec +import org.scalajs.jasmine.Suite + +/** This class is passed to the actual jasmine framework as a reporter */ +class JasmineTestReporter(testOutput: TestOutput) { + private var currentSuite: Suite = _ + + @JSExport + def reportRunnerStarting(): Unit = { + testOutput.log.info("") + } + + @JSExport + def reportSpecStarting(spec: Spec): Unit = { + if (currentSuite != spec.suite) { + currentSuite = spec.suite + info(currentSuite.description) + } + } + + @JSExport + 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 + } + } + + @JSExport + 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("") + } + + @JSExport + 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: testOutput.Color, 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 = StackTrace.extract(r.trace).takeWhile { stackElem => + (stackElem.getFileName == null || + !stackElem.getFileName.endsWith("jasmine.js")) + } + + if (stack.isEmpty) + testOutput.failure(s" $message") + else + testOutput.error(s" $message", stack) + } + } + } + +} diff --git a/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/TestSuiteContext.scala b/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/TestSuiteContext.scala new file mode 100644 index 0000000..827eefd --- /dev/null +++ b/jasmine-test-framework/src/main/scala/org/scalajs/jasminetest/TestSuiteContext.scala @@ -0,0 +1,55 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Framework ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package org.scalajs.jasminetest + +trait TestSuiteContext { + def describe(title: String)(test: => Unit): Unit + def it(title: String)(test: => Unit): Unit + def xdescribe(title: String)(test: => Unit): Unit + def xit(title: String)(test: => Unit): Unit + + def when(tag: String): TestSuiteContext = + if (JasmineTestFramework.hasTag(tag)) this + else new TestSuiteContext.IgnoredContext(this) + + def whenAll(tags: String*): TestSuiteContext = + if (tags.forall(JasmineTestFramework.hasTag)) this + else new TestSuiteContext.IgnoredContext(this) + + def whenAny(tags: String*): TestSuiteContext = + if (tags.exists(JasmineTestFramework.hasTag)) this + else new TestSuiteContext.IgnoredContext(this) + + def unless(tag: String): TestSuiteContext = + if (!JasmineTestFramework.hasTag(tag)) this + else new TestSuiteContext.IgnoredContext(this) + + def unlessAll(tags: String*): TestSuiteContext = + if (!tags.forall(JasmineTestFramework.hasTag)) this + else new TestSuiteContext.IgnoredContext(this) + + def unlessAny(tags: String*): TestSuiteContext = + if (!tags.exists(JasmineTestFramework.hasTag)) this + else new TestSuiteContext.IgnoredContext(this) +} + +object TestSuiteContext { + private class IgnoredContext( + baseContext: TestSuiteContext) extends TestSuiteContext { + def describe(title: String)(test: => Unit): Unit = + baseContext.xdescribe(title)(test) + def it(title: String)(test: => Unit): Unit = + baseContext.xit(title)(test) + def xdescribe(title: String)(test: => Unit): Unit = + baseContext.xdescribe(title)(test) + def xit(title: String)(test: => Unit): Unit = + baseContext.xit(title)(test) + } +} |