Use async tests on Kotlin/JS with Kotlin version 1.2.20-eap-33
diff --git a/js/example-frontend-js/build.gradle b/js/example-frontend-js/build.gradle
index e5a945e..625f580 100644
--- a/js/example-frontend-js/build.gradle
+++ b/js/example-frontend-js/build.gradle
@@ -40,9 +40,3 @@
 task start(type: NpmTask, dependsOn: bundle) {
     args = ["run", "start"]
 }
-
-// we have not tests but kotlin-dce-js still tries to work with them and crashed.
-// todo: Remove when KT-22028 is fixed
-afterEvaluate {
-    tasks.unpackDependenciesTestKotlinJs.enabled = false
-}
diff --git a/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/PromiseTest.kt b/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/PromiseTest.kt
index b5c058f..5f9414c 100644
--- a/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/PromiseTest.kt
+++ b/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/PromiseTest.kt
@@ -19,8 +19,6 @@
 import kotlin.js.Promise
 import kotlin.test.*
 
-// :todo: This test does not actually test anything because of KT-21970 JS: Support async tests in Mocha and others
-// One should watch for errors in the console to see if there were any failures (the test would still pass)
 class PromiseTest : TestBase() {
     @Test
     fun testPromiseResolvedAsDeferred() = promise {
@@ -33,10 +31,13 @@
     
     @Test
     fun testPromiseRejectedAsDeferred() = promise {
+        lateinit var promiseReject: (Throwable) -> Unit
         val promise = Promise<String> { _, reject ->
-            reject(TestException("Rejected"))
+            promiseReject = reject
         }
         val deferred = promise.asDeferred()
+        // reject after converting to deferred to avoid "Unhandled promise rejection" warnings
+        promiseReject(TestException("Rejected"))
         try {
             deferred.await()
             expectUnreached()
diff --git a/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt b/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt
index 2f46ef7..7872b4f 100644
--- a/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt
+++ b/js/kotlinx-coroutines-core-js/src/test/kotlin/kotlinx/coroutines/experimental/TestBase.kt
@@ -16,6 +16,8 @@
 
 package kotlinx.coroutines.experimental
 
+import kotlin.js.*
+
 public actual open class TestBase actual constructor() {
     public actual val isStressTest: Boolean = false
     public actual val stressTestMultiplier: Int = 1
@@ -66,29 +68,33 @@
     ) {
         var exCount = 0
         var ex: Throwable? = null
-        try {
-            runBlocking(block = block, context = CoroutineExceptionHandler { context, e ->
-                if (e is CancellationException) return@CoroutineExceptionHandler // are ignored
-                exCount++
-                if (exCount > unhandled.size)
-                    error("Too many unhandled exceptions $exCount, expected ${unhandled.size}", e)
-                if (!unhandled[exCount - 1](e))
-                    error("Unhandled exception was unexpected", e)
-                context[Job]?.cancel(e)
-            })
-        } catch (e: Throwable) {
+        val promise = promise(block = block, context = CoroutineExceptionHandler { context, e ->
+            if (e is CancellationException) return@CoroutineExceptionHandler // are ignored
+            exCount++
+            if (exCount > unhandled.size)
+                error("Too many unhandled exceptions $exCount, expected ${unhandled.size}", e)
+            if (!unhandled[exCount - 1](e))
+                error("Unhandled exception was unexpected", e)
+            context[Job]?.cancel(e)
+        }).catch { e ->
             ex = e
             if (expected != null) {
                 if (!expected(e))
                     error("Unexpected exception", e)
             } else
                 throw e
-        } finally {
+        }.finally {
             if (ex == null && expected != null) error("Exception was expected but none produced")
+            if (exCount < unhandled.size)
+                error("Too few unhandled exceptions $exCount, expected ${unhandled.size}")
+            error?.let { throw it }
+            check(actionIndex == 0 || finished) { "Expecting that 'finish(...)' was invoked, but it was not" }
         }
-        if (exCount < unhandled.size)
-            error("Too few unhandled exceptions $exCount, expected ${unhandled.size}")
-        error?.let { throw it }
-        check(actionIndex == 0 || finished) { "Expecting that 'finish(...)' was invoked, but it was not" }
+        // todo: This is a work-around for missing suspend tests, see KT-22228
+        @Suppress("UnsafeCastFromDynamic")
+        return promise.asDynamic()
     }
 }
+
+private fun <T> Promise<T>.finally(block: () -> Unit): Promise<T> =
+    then(onFulfilled = { value -> block(); value }, onRejected = { ex -> block(); throw ex })