Refactoring, more nested test generation, split QUnit 1 and 2 (is that a good idea at all)
diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/JSTestGenerator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/JSTestGenerator.kt
index 882a31a..f269dbb 100644
--- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/JSTestGenerator.kt
+++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/JSTestGenerator.kt
@@ -17,13 +17,11 @@
 package org.jetbrains.kotlin.js.translate.test
 
 import org.jetbrains.kotlin.descriptors.*
-import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
 import org.jetbrains.kotlin.js.backend.ast.*
 import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator
 import org.jetbrains.kotlin.js.translate.context.TranslationContext
 import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator
 import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.name.FqNameUnsafe
 import org.jetbrains.kotlin.name.Name
 import org.jetbrains.kotlin.resolve.DescriptorUtils
 import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
@@ -42,25 +40,39 @@
         return JsNameRef(name, JsNameRef("Kotlin"))
     }
 
-    fun generateTestCalls(moduleDescriptor: ModuleDescriptor) = generateTestCalls(moduleDescriptor, FqName.ROOT)
+    fun generateTestCalls(moduleDescriptor: ModuleDescriptor) {
+        val rootFunction = JsFunction(context.scope(), JsBlock(), "root suite function")
 
-    private fun generateTestCalls(moduleDescriptor: ModuleDescriptor, packageName: FqName) {
+        generateTestCalls(moduleDescriptor, FqName.ROOT, rootFunction)
+
+        if (!rootFunction.body.isEmpty) {
+            context.addTopLevelStatement(JsInvocation(suiteRef, JsStringLiteral(""), rootFunction).makeStmt())
+        }
+    }
+
+    private fun generateTestCalls(moduleDescriptor: ModuleDescriptor, packageName: FqName, parentFun: JsFunction) {
         for (packageDescriptor in moduleDescriptor.getPackage(packageName).fragments) {
             if (DescriptorUtils.getContainingModule(packageDescriptor) !== moduleDescriptor) continue
 
             packageDescriptor.getMemberScope().getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS, MemberScope.ALL_NAME_FILTER).forEach {
                 if (it is ClassDescriptor) {
-                    generateTestFunctions(it)
+                    generateTestFunctions(it, parentFun)
                 }
             }
         }
 
         for (subpackageName in moduleDescriptor.getSubPackagesOf(packageName, MemberScope.ALL_NAME_FILTER)) {
-            generateTestCalls(moduleDescriptor, subpackageName)
+            val subPackageFunction = JsFunction(context.scope(), JsBlock(), "${subpackageName.asString()} package suite function")
+
+            generateTestCalls(moduleDescriptor, subpackageName, subPackageFunction)
+
+            if (!subPackageFunction.body.isEmpty) {
+                parentFun.body.statements += JsInvocation(suiteRef, JsStringLiteral(subpackageName.shortName().asString()), subPackageFunction).makeStmt()
+            }
         }
     }
 
-    private fun generateTestFunctions(classDescriptor: ClassDescriptor) {
+    private fun generateTestFunctions(classDescriptor: ClassDescriptor, parentFun: JsFunction) {
         if (classDescriptor.modality === Modality.ABSTRACT) return
 
         val suiteFunction = JsFunction(context.scope(), JsBlock(), "suite function")
@@ -74,7 +86,7 @@
         if (!suiteFunction.body.isEmpty) {
             val suiteName = JsStringLiteral(classDescriptor.name.toString())
 
-            context.addTopLevelStatement(JsInvocation(classDescriptor.ref, suiteName, suiteFunction).makeStmt())
+            parentFun.body.statements += JsInvocation(classDescriptor.ref, suiteName, suiteFunction).makeStmt()
         }
     }
 
@@ -124,13 +136,13 @@
      * }
      */
     private val FunctionDescriptor.isTest
-            get() = annotationFinder("Test", "kotlin.test", "org.junit")
+        get() = annotationFinder("Test", "kotlin.test", "org.junit") // Support both ways for now.
 
     private val DeclarationDescriptor.isIgnored
-            get() = annotationFinder("Ignore", "kotlin.test")
+        get() = annotationFinder("Ignore", "kotlin.test")
 
     private val DeclarationDescriptor.isFocused
-            get() = annotationFinder("Only", "kotlin.test")
+        get() = annotationFinder("Only", "kotlin.test")
 
     private fun DeclarationDescriptor.annotationFinder(shortName: String, vararg packages: String) = packages.any { packageName ->
         annotations.hasAnnotation(FqName("$packageName.$shortName"))
diff --git a/libraries/kotlin.test/js/it/package.json b/libraries/kotlin.test/js/it/package.json
index 59e2ed6..390f9de 100644
--- a/libraries/kotlin.test/js/it/package.json
+++ b/libraries/kotlin.test/js/it/package.json
@@ -17,7 +17,7 @@
     "jasmine": "^2.6.0",
     "jest": "^20.0.4",
     "mocha": "^3.4.2",
-    "qunit": "^1.0.0",
+    "qunitjs": "^2.3.3",
     "tape": "^4.6.3",
     "watchify": "^3.9.0"
   },
diff --git a/libraries/kotlin.test/js/src/main/kotlin/TestApi.kt b/libraries/kotlin.test/js/src/main/kotlin/TestApi.kt
index 3ac71c0..4f8e608 100644
--- a/libraries/kotlin.test/js/src/main/kotlin/TestApi.kt
+++ b/libraries/kotlin.test/js/src/main/kotlin/TestApi.kt
@@ -67,14 +67,14 @@
 internal var currentAdapter: FrameworkAdapter = detectAdapter()
 
 internal fun detectAdapter() = when {
-    isQUnit() -> QUnitAdapter()
+    isQUnit2() -> QUnit2Adapter()
     isJasmine() -> JasmineAdapter()
     isMocha() -> MochaAdapter()
     else -> BareAdapter()
 }
 
 private val NAME_TO_ADAPTER: Map<String, () -> FrameworkAdapter> = mapOf(
-                "qunit" to ::QUnitAdapter,
+                "qunit" to { if (isQUnit1()) QUnit2Adapter() else QUnit2Adapter() },
                 "jasmine" to ::JasmineAdapter,
                 "mocha" to ::MochaAdapter,
                 "auto" to ::detectAdapter)
diff --git a/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/BareAdapter.kt b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/BareAdapter.kt
index eb96f75..8e8ec5a 100644
--- a/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/BareAdapter.kt
+++ b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/BareAdapter.kt
@@ -16,6 +16,8 @@
 
 package kotlin.test.adapters
 
+import kotlin.test.FrameworkAdapter
+
 /**
  * A fallback adapter for the case when no framework is detected.
  */
diff --git a/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/Externals.kt b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/Externals.kt
index b33752a..5372974 100644
--- a/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/Externals.kt
+++ b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/Externals.kt
@@ -40,10 +40,12 @@
 external fun fdescribe(name: String, fn: () -> Unit)
 
 
-internal fun isQUnit() = jsTypeOf(QUnit) !== "undefined"
-
 private fun isFunction(a: String) = js("typeof a === 'function'")
 
+internal fun isQUnit1() = jsTypeOf(QUnit) !== "undefined" && isFunction("ok")
+
+internal fun isQUnit2() = jsTypeOf(QUnit) !== "undefined" && isFunction("QUnit.module.skip") && isFunction("QUnit.module.only")
+
 internal fun isJasmine() = isFunction("describe") && isFunction("it") && isFunction("fit")
 
 internal fun isMocha() = isFunction("describe") && isFunction("it") && isFunction("it.only")
diff --git a/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/FrameworkAdapter.kt b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/FrameworkAdapter.kt
deleted file mode 100644
index 0c46294..0000000
--- a/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/FrameworkAdapter.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2010-2017 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package kotlin.test.adapters
-
-external interface FrameworkAdapter {
-    fun suite(name: String, suiteFn: () -> Unit)
-
-    fun xsuite(name: String, suiteFn: () -> Unit)
-
-    fun fsuite(name: String, suiteFn: () -> Unit)
-
-    fun test(name: String, testFn: () -> Unit)
-
-    fun xtest(name: String, testFn: () -> Unit)
-
-    fun ftest(name: String, testFn: () -> Unit)
-}
diff --git a/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/JasmineAdapter.kt b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/JasmineAdapter.kt
index 0af1dc0..2f1d87a 100644
--- a/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/JasmineAdapter.kt
+++ b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/JasmineAdapter.kt
@@ -16,6 +16,8 @@
 
 package kotlin.test.adapters
 
+import kotlin.test.FrameworkAdapter
+
 /**
  * Jasmine adapter
  */
diff --git a/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/MochaAdapter.kt b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/MochaAdapter.kt
index 51a392d..19d532e 100644
--- a/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/MochaAdapter.kt
+++ b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/MochaAdapter.kt
@@ -16,6 +16,8 @@
 
 package kotlin.test.adapters
 
+import kotlin.test.FrameworkAdapter
+
 /**
  * Mocha adapter
  */
diff --git a/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/QUnitAdapter.kt b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/QUnit1Adapter.kt
similarity index 71%
rename from libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/QUnitAdapter.kt
rename to libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/QUnit1Adapter.kt
index 3eeeaa2..97eb489 100644
--- a/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/QUnitAdapter.kt
+++ b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/QUnit1Adapter.kt
@@ -16,12 +16,13 @@
 
 package kotlin.test.adapters
 
+import kotlin.test.FrameworkAdapter
 import kotlin.test.assertHook
 
 /**
  * QUnit adapter
  */
-internal class QUnitAdapter: BareAdapter() {
+internal class QUnit1Adapter: BareAdapter() {
 
     override fun runTest(testFn: () -> Unit,
                          names: Sequence<String>,
@@ -38,18 +39,8 @@
     }
 
     private fun wrapTest(testFn: () -> Unit): (dynamic) -> Unit = { assert ->
-        if (js("typeof assert !== 'function'")) {
-            assertHook = { result -> assert.ok(result.result, result.lazyMessage()) }
-        }
-        else {
-            assertHook = { result ->
-                val data = js("{}")
-                data.result = result.result
-                data.actual = result.actual
-                data.expected = result.expected
-                data.message = result.lazyMessage()
-                assert.pushResult(data)
-            }
+        assertHook = { testResult ->
+          assert.ok(testResult.result, testResult.lazyMessage())
         }
         testFn()
     }