diff --git a/Kotlin.iml b/Kotlin.iml
index 2693539c82d..55f858484e3 100644
--- a/Kotlin.iml
+++ b/Kotlin.iml
@@ -26,6 +26,8 @@
+
+
@@ -81,4 +83,4 @@
-
\ No newline at end of file
+
diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.java b/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.java
index 327685e929c..6dce3292ffb 100644
--- a/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.java
+++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.java
@@ -575,6 +575,17 @@ public class DescriptorUtils {
@NotNull
public static FunctionDescriptor getFunctionByName(@NotNull MemberScope scope, @NotNull Name name) {
+ FunctionDescriptor result = getFunctionByNameOrNull(scope, name);
+
+ if (result == null) {
+ throw new IllegalStateException("Function not found");
+ }
+
+ return result;
+ }
+
+ @Nullable
+ public static FunctionDescriptor getFunctionByNameOrNull(@NotNull MemberScope scope, @NotNull Name name) {
Collection functions = scope.getContributedDescriptors(DescriptorKindFilter.FUNCTIONS,
MemberScope.Companion.getALL_NAME_FILTER());
for (DeclarationDescriptor d : functions) {
@@ -583,7 +594,7 @@ public class DescriptorUtils {
}
}
- throw new IllegalStateException("Function not found");
+ return null;
}
@NotNull
diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/JetTestFunctionDetector.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/JetTestFunctionDetector.java
deleted file mode 100644
index 423c9c31281..00000000000
--- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/JetTestFunctionDetector.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright 2010-2015 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 org.jetbrains.kotlin.js.translate.general;
-
-import com.google.common.collect.Lists;
-import com.intellij.util.containers.ContainerUtil;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.kotlin.descriptors.*;
-import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
-import org.jetbrains.kotlin.descriptors.annotations.Annotations;
-import org.jetbrains.kotlin.name.FqName;
-import org.jetbrains.kotlin.resolve.DescriptorUtils;
-import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter;
-import org.jetbrains.kotlin.resolve.scopes.MemberScope;
-import org.jetbrains.kotlin.types.KotlinType;
-
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Helps find functions which are annotated with a @Test annotation from junit
- */
-public class JetTestFunctionDetector {
- private JetTestFunctionDetector() {
- }
-
- private static boolean isTest(@NotNull FunctionDescriptor functionDescriptor) {
- Annotations annotations = functionDescriptor.getAnnotations();
- for (AnnotationDescriptor annotation : annotations) {
- // TODO ideally we should find the fully qualified name here...
- KotlinType type = annotation.getType();
- String name = type.toString();
- if (name.equals("Test")) {
- return true;
- }
- }
-
- /*
- if (function.getName().startsWith("test")) {
- List parameters = function.getValueParameters();
- return parameters.size() == 0;
- }
- */
- return false;
- }
-
- @NotNull
- public static List getTestFunctionDescriptors(
- @NotNull ModuleDescriptor moduleDescriptor
- ) {
- List answer = Lists.newArrayList();
- getTestFunctions(FqName.ROOT, moduleDescriptor, answer);
- return answer;
- }
-
- private static void getTestFunctions(
- @NotNull FqName packageName,
- @NotNull ModuleDescriptor moduleDescriptor,
- @NotNull List foundFunctions
- ) {
- for (PackageFragmentDescriptor packageDescriptor : moduleDescriptor.getPackage(packageName).getFragments()) {
- if (DescriptorUtils.getContainingModule(packageDescriptor) != moduleDescriptor) continue;
- Collection descriptors = packageDescriptor.getMemberScope().getContributedDescriptors(
- DescriptorKindFilter.CLASSIFIERS, MemberScope.Companion.getALL_NAME_FILTER());
- for (DeclarationDescriptor descriptor : descriptors) {
- if (descriptor instanceof ClassDescriptor) {
- getTestFunctions((ClassDescriptor) descriptor, foundFunctions);
- }
- }
- }
-
- for (FqName subpackageName : moduleDescriptor.getSubPackagesOf(packageName, MemberScope.Companion.getALL_NAME_FILTER())) {
- getTestFunctions(subpackageName, moduleDescriptor, foundFunctions);
- }
- }
-
- private static void getTestFunctions(
- @NotNull ClassDescriptor classDescriptor,
- @NotNull List foundFunctions
- ) {
- if (classDescriptor.getModality() == Modality.ABSTRACT) return;
-
-
- Collection allDescriptors = classDescriptor.getUnsubstitutedMemberScope().getContributedDescriptors(
- DescriptorKindFilter.FUNCTIONS, MemberScope.Companion.getALL_NAME_FILTER());
- List testFunctions = ContainerUtil.mapNotNull(
- allDescriptors,
- descriptor-> {
- if (descriptor instanceof FunctionDescriptor) {
- FunctionDescriptor functionDescriptor = (FunctionDescriptor) descriptor;
- if (isTest(functionDescriptor)) return functionDescriptor;
- }
-
- return null;
- });
-
-
- foundFunctions.addAll(testFunctions);
-
- }
-}
diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java
index 8637e456966..9a4d4531157 100644
--- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java
+++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java
@@ -39,8 +39,6 @@ import org.jetbrains.kotlin.js.translate.declaration.FileDeclarationVisitor;
import org.jetbrains.kotlin.js.translate.expression.ExpressionVisitor;
import org.jetbrains.kotlin.js.translate.expression.PatternTranslator;
import org.jetbrains.kotlin.js.translate.test.JSTestGenerator;
-import org.jetbrains.kotlin.js.translate.test.JSTester;
-import org.jetbrains.kotlin.js.translate.test.QUnitTester;
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils;
import org.jetbrains.kotlin.js.translate.utils.BindingUtils;
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
@@ -395,8 +393,8 @@ public final class Translation {
) {
StaticContext staticContext = new StaticContext(trace, config, moduleDescriptor);
TranslationContext context = TranslationContext.rootContext(staticContext);
- JSTester tester = new QUnitTester(context);
- JSTestGenerator.generateTestCalls(context, moduleDescriptor, tester);
+
+ new JSTestGenerator(context).generateTestCalls(moduleDescriptor);
return staticContext.getFragment();
}
diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/CommonUnitTester.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/CommonUnitTester.java
deleted file mode 100644
index 5cdf3e79ed0..00000000000
--- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/CommonUnitTester.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2010-2015 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 org.jetbrains.kotlin.js.translate.test;
-
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.kotlin.js.backend.ast.*;
-import org.jetbrains.kotlin.js.translate.context.TranslationContext;
-
-public abstract class CommonUnitTester extends JSTester {
- public CommonUnitTester(@NotNull TranslationContext context) {
- super(context);
- }
-
- @Override
- public void constructTestMethodInvocation(@NotNull JsExpression functionToTestCall,
- @NotNull JsStringLiteral testName) {
- JsFunction functionToTest = new JsFunction(getContext().scope(), "test function");
- functionToTest.setBody(new JsBlock(functionToTestCall.makeStmt()));
- getContext().addTopLevelStatement(new JsInvocation(getTestMethodRef(), testName, functionToTest).makeStmt());
- }
-
- @NotNull
- protected abstract JsExpression getTestMethodRef();
-}
diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/JSTestGenerator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/JSTestGenerator.java
deleted file mode 100644
index 3f4e1220e13..00000000000
--- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/JSTestGenerator.java
+++ /dev/null
@@ -1,68 +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 org.jetbrains.kotlin.js.translate.test;
-
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.kotlin.descriptors.ClassDescriptor;
-import org.jetbrains.kotlin.descriptors.FunctionDescriptor;
-import org.jetbrains.kotlin.descriptors.ModuleDescriptor;
-import org.jetbrains.kotlin.js.backend.ast.JsExpression;
-import org.jetbrains.kotlin.js.backend.ast.JsNew;
-import org.jetbrains.kotlin.js.backend.ast.JsStringLiteral;
-import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator;
-import org.jetbrains.kotlin.js.translate.context.TranslationContext;
-import org.jetbrains.kotlin.js.translate.general.JetTestFunctionDetector;
-import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator;
-import org.jetbrains.kotlin.resolve.DescriptorUtils;
-
-import java.util.Collections;
-import java.util.List;
-
-//TODO: use method object instead of static functions
-public final class JSTestGenerator {
- private JSTestGenerator() {
- }
-
- public static void generateTestCalls(@NotNull TranslationContext context,
- @NotNull ModuleDescriptor moduleDescriptor, @NotNull JSTester tester) {
- List functionDescriptors =
- JetTestFunctionDetector.getTestFunctionDescriptors(moduleDescriptor);
- doGenerateTestCalls(functionDescriptors, context, tester);
- }
-
- private static void doGenerateTestCalls(@NotNull List functionDescriptors,
- @NotNull TranslationContext context, @NotNull JSTester jsTester) {
- for (FunctionDescriptor functionDescriptor : functionDescriptors) {
- ClassDescriptor classDescriptor = DescriptorUtils.getContainingClass(functionDescriptor);
- if (classDescriptor == null) {
- return;
- }
- generateCodeForTestMethod(context, functionDescriptor, classDescriptor, jsTester);
- }
- }
-
- private static void generateCodeForTestMethod(@NotNull TranslationContext context,
- @NotNull FunctionDescriptor functionDescriptor,
- @NotNull ClassDescriptor classDescriptor, @NotNull JSTester tester) {
- JsExpression expression = ReferenceTranslator.translateAsValueReference(classDescriptor, context);
- JsNew testClass = new JsNew(expression);
- JsExpression functionToTestCall =
- CallTranslator.INSTANCE.buildCall(context, functionDescriptor, Collections.emptyList(), testClass);
- JsStringLiteral testName = new JsStringLiteral(classDescriptor.getName() + "." + functionDescriptor.getName());
- tester.constructTestMethodInvocation(functionToTestCall, testName);
- }
-}
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
new file mode 100644
index 00000000000..ea1faa5b962
--- /dev/null
+++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/JSTestGenerator.kt
@@ -0,0 +1,114 @@
+/*
+ * 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 org.jetbrains.kotlin.js.translate.test
+
+import org.jetbrains.kotlin.descriptors.*
+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
+import org.jetbrains.kotlin.resolve.scopes.MemberScope
+
+class JSTestGenerator(val context: TranslationContext) {
+
+ fun generateTestCalls(moduleDescriptor: ModuleDescriptor) {
+ generateTestCalls(moduleDescriptor, FqName.ROOT)
+ }
+
+ private fun generateTestCalls(moduleDescriptor: ModuleDescriptor, packageName: FqName) {
+ val packageFunction = JsFunction(context.scope(), JsBlock(), "${packageName.asString()} package suite function")
+
+ 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, packageFunction)
+ }
+ }
+ }
+
+ if (!packageFunction.body.isEmpty) {
+ val suiteName = JsStringLiteral(packageName.asString())
+ context.addTopLevelStatement(JsInvocation(suiteRef, suiteName, JsBooleanLiteral(false), packageFunction).makeStmt())
+ }
+
+ for (subpackageName in moduleDescriptor.getSubPackagesOf(packageName, MemberScope.ALL_NAME_FILTER)) {
+ generateTestCalls(moduleDescriptor, subpackageName)
+ }
+ }
+
+ private fun generateTestFunctions(classDescriptor: ClassDescriptor, parentFun: JsFunction) {
+ if (classDescriptor.modality === Modality.ABSTRACT) return
+
+ val suiteFunction = JsFunction(context.scope(), JsBlock(), "suite function")
+
+ classDescriptor.unsubstitutedMemberScope.getContributedDescriptors(DescriptorKindFilter.FUNCTIONS, MemberScope.ALL_NAME_FILTER).forEach {
+ if (it is FunctionDescriptor && it.isTest) {
+ generateCodeForTestMethod(it, classDescriptor, suiteFunction)
+ }
+ }
+
+ if (!suiteFunction.body.isEmpty) {
+ val suiteName = JsStringLiteral(classDescriptor.name.toString())
+
+ parentFun.body.statements += JsInvocation(suiteRef, suiteName, JsBooleanLiteral(classDescriptor.isIgnored), suiteFunction).makeStmt()
+ }
+ }
+
+ private fun generateCodeForTestMethod(functionDescriptor: FunctionDescriptor, classDescriptor: ClassDescriptor, parentFun: JsFunction) {
+ val functionToTest = generateTestFunction(functionDescriptor, classDescriptor, parentFun.scope)
+
+ val testName = JsStringLiteral(functionDescriptor.name.toString())
+ parentFun.body.statements += JsInvocation(testRef, testName, JsBooleanLiteral(functionDescriptor.isIgnored), functionToTest).makeStmt()
+ }
+
+ private fun generateTestFunction(functionDescriptor: FunctionDescriptor, classDescriptor: ClassDescriptor, scope: JsScope): JsFunction {
+ val expression = ReferenceTranslator.translateAsValueReference(classDescriptor, context)
+ val testClass = JsNew(expression)
+ val functionToTestCall = CallTranslator.buildCall(context, functionDescriptor, emptyList(), testClass)
+ val functionToTest = JsFunction(scope, "test function")
+ functionToTest.body = JsBlock(functionToTestCall.makeStmt())
+
+ return functionToTest
+ }
+
+ private val suiteRef: JsExpression by lazy { findFunction("suite") }
+ private val testRef: JsExpression by lazy { findFunction("test") }
+
+ private fun findFunction(name: String): JsExpression {
+ val descriptor = DescriptorUtils.getFunctionByNameOrNull(
+ context.currentModule.getPackage(FqNameUnsafe("kotlin.test").toSafe()).memberScope,
+ Name.identifier(name)) ?: return JsNameRef(name, JsNameRef("Kotlin"))
+ return ReferenceTranslator.translateAsValueReference(descriptor, context)
+ }
+
+ private val FunctionDescriptor.isTest
+ get() = annotationFinder("Test", "kotlin.test")
+
+ private val DeclarationDescriptor.isIgnored
+ get() = annotationFinder("Ignore", "kotlin.test")
+
+ private fun DeclarationDescriptor.annotationFinder(shortName: String, vararg packages: String) = packages.any { packageName ->
+ annotations.hasAnnotation(FqName("$packageName.$shortName"))
+ }
+}
diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/JSTester.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/JSTester.java
deleted file mode 100644
index a44c9254f9a..00000000000
--- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/JSTester.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2010-2015 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 org.jetbrains.kotlin.js.translate.test;
-
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.kotlin.js.backend.ast.JsExpression;
-import org.jetbrains.kotlin.js.backend.ast.JsStringLiteral;
-import org.jetbrains.kotlin.js.translate.context.TranslationContext;
-
-public abstract class JSTester {
-
- @NotNull
- private final TranslationContext context;
-
- public JSTester(@NotNull TranslationContext context) {
- this.context = context;
- }
-
- public abstract void constructTestMethodInvocation(@NotNull JsExpression call, @NotNull JsStringLiteral name);
-
- @NotNull
- protected TranslationContext getContext() {
- return context;
- }
-}
diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/QUnitTester.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/QUnitTester.java
deleted file mode 100644
index c6b31305d66..00000000000
--- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/QUnitTester.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2010-2015 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 org.jetbrains.kotlin.js.translate.test;
-
-import org.jetbrains.kotlin.js.backend.ast.JsExpression;
-import org.jetbrains.kotlin.js.backend.ast.JsNameRef;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.kotlin.js.translate.context.TranslationContext;
-
-public final class QUnitTester extends CommonUnitTester {
- public QUnitTester(@NotNull TranslationContext context) {
- super(context);
- }
-
- @NotNull
- private static final JsNameRef TEST_FUN_REF = new JsNameRef("test", "QUnit");
-
- @Override
- @NotNull
- protected JsExpression getTestMethodRef() {
- return TEST_FUN_REF;
- }
-}
diff --git a/libraries/kotlin.test/common/src/test/kotlin/kotlin/test/tests/BasicAssertionsTest.kt b/libraries/kotlin.test/common/src/test/kotlin/kotlin/test/tests/BasicAssertionsTest.kt
index 00948a945cd..6dacb8f08ea 100644
--- a/libraries/kotlin.test/common/src/test/kotlin/kotlin/test/tests/BasicAssertionsTest.kt
+++ b/libraries/kotlin.test/common/src/test/kotlin/kotlin/test/tests/BasicAssertionsTest.kt
@@ -1,7 +1,7 @@
package kotlin.test.tests
-import org.junit.*
import kotlin.test.*
+import org.junit.Test
class BasicAssertionsTest {
@Test
diff --git a/libraries/kotlin.test/js/it/.gitignore b/libraries/kotlin.test/js/it/.gitignore
new file mode 100644
index 00000000000..3c3629e647f
--- /dev/null
+++ b/libraries/kotlin.test/js/it/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/libraries/kotlin.test/js/it/build.gradle b/libraries/kotlin.test/js/it/build.gradle
new file mode 100644
index 00000000000..6206725604a
--- /dev/null
+++ b/libraries/kotlin.test/js/it/build.gradle
@@ -0,0 +1,75 @@
+plugins {
+ id "com.moowork.node" version "1.2.0"
+}
+
+description = 'Kotlin-test integration tests for JS'
+
+apply plugin: 'kotlin-platform-js'
+
+dependencies {
+ compile project(':kotlin-test:kotlin-test-js')
+}
+
+
+sourceSets {
+ main.kotlin.srcDirs += 'src'
+ test.kotlin.srcDirs += 'test'
+}
+
+
+compileKotlin2Js {
+ kotlinOptions {
+ moduleKind = "commonjs"
+ }
+}
+
+compileTestKotlin2Js {
+ kotlinOptions {
+ moduleKind = "commonjs"
+ }
+}
+
+compileKotlin2Js.doLast {
+ configurations.compile.each { File file ->
+ copy {
+ includeEmptyDirs = false
+
+ from zipTree(file.absolutePath)
+ into "${buildDir}/classes/"
+ include { fileTreeElement ->
+ def path = fileTreeElement.path
+ path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/"))
+ }
+ }
+ }
+}
+
+node {
+ version = '6.11.0'
+ download = true
+}
+
+task testJest(type: NpmTask, dependsOn: [compileTestKotlin2Js, compileTestKotlin2Js, npmInstall]) {
+ args = ['run', 'test-jest']
+}
+
+task testJasmine(type: NpmTask, dependsOn: [compileTestKotlin2Js, compileTestKotlin2Js, npmInstall]) {
+ args = ['run', 'test-jasmine']
+}
+
+task testMocha(type: NpmTask, dependsOn: [compileTestKotlin2Js, compileTestKotlin2Js, npmInstall]) {
+ args = ['run', 'test-mocha']
+}
+
+task testQunit(type: NpmTask, dependsOn: [compileTestKotlin2Js, compileTestKotlin2Js, npmInstall]) {
+ args = ['run', 'test-qunit']
+}
+
+task testTape(type: NpmTask, dependsOn: [compileTestKotlin2Js, compileTestKotlin2Js, npmInstall]) {
+ args = ['run', 'test-tape']
+}
+
+[testJest, testJasmine, testMocha, testQunit, testTape].each {
+ check.dependsOn it
+ it.group = "verification"
+}
diff --git a/libraries/kotlin.test/js/it/js/expected-outcomes.js b/libraries/kotlin.test/js/it/js/expected-outcomes.js
new file mode 100644
index 00000000000..c775140f115
--- /dev/null
+++ b/libraries/kotlin.test/js/it/js/expected-outcomes.js
@@ -0,0 +1,10 @@
+module.exports = {
+ 'SimpleTest testFoo': 'fail',
+ 'SimpleTest testBar': 'pass',
+ 'SimpleTest testFooWrong': 'pending',
+ 'TestTest emptyTest': 'pending',
+ 'org OrgTest test': 'pass',
+ 'org.some SomeTest someIsBetterThanNothing': 'pass',
+ 'org.some.name NameTest test': 'pass',
+ 'org.other.name NameTest nameIsOk': 'pass'
+};
\ No newline at end of file
diff --git a/libraries/kotlin.test/js/it/js/jasmine-reporter.js b/libraries/kotlin.test/js/it/js/jasmine-reporter.js
new file mode 100644
index 00000000000..58403ff0120
--- /dev/null
+++ b/libraries/kotlin.test/js/it/js/jasmine-reporter.js
@@ -0,0 +1,22 @@
+var Tester = require('./test-result-checker');
+var tester = new Tester(require('./expected-outcomes'));
+
+process.on('exit', function() {
+ tester.end();
+});
+
+jasmine.getEnv().addReporter({
+ specDone: function(result) {
+ var status = result.status;
+ var name = result.fullName.trim();
+ if (status === 'passed') {
+ tester.passed(name);
+ }
+ else if (status === 'failed') {
+ tester.failed(name);
+ }
+ else {
+ tester.pending(name);
+ }
+ }
+});
\ No newline at end of file
diff --git a/libraries/kotlin.test/js/it/js/jest-reporter.js b/libraries/kotlin.test/js/it/js/jest-reporter.js
new file mode 100644
index 00000000000..d3568912ea1
--- /dev/null
+++ b/libraries/kotlin.test/js/it/js/jest-reporter.js
@@ -0,0 +1,22 @@
+var Tester = require('./test-result-checker');
+var expectedOutcomes = require('./expected-outcomes');
+
+module.exports = function (results) {
+ var tester = new Tester(expectedOutcomes);
+ var testResults = results.testResults[0].testResults;
+ for (var i = 0; i < testResults.length; i++) {
+ var tr = testResults[i];
+
+ var name = tr.fullName.trim();
+ if (tr.status === 'passed') {
+ tester.passed(name);
+ }
+ else if (tr.status === 'failed') {
+ tester.failed(name);
+ }
+ else {
+ tester.pending(name);
+ }
+ }
+ tester.end();
+};
\ No newline at end of file
diff --git a/libraries/kotlin.test/js/it/js/mocha-reporter.js b/libraries/kotlin.test/js/it/js/mocha-reporter.js
new file mode 100644
index 00000000000..4b465bba631
--- /dev/null
+++ b/libraries/kotlin.test/js/it/js/mocha-reporter.js
@@ -0,0 +1,25 @@
+var mocha = require('mocha');
+var Tester = require('./test-result-checker');
+var expectedOutcomes = require('./expected-outcomes');
+
+module.exports = function (runner) {
+ mocha.reporters.Base.call(this, runner);
+
+ var tester = new Tester(expectedOutcomes);
+
+ runner.on('pass', function (test) {
+ tester.passed(test.fullTitle().trim());
+ });
+
+ runner.on('fail', function (test, err) {
+ tester.failed(test.fullTitle().trim());
+ });
+
+ runner.on('pending', function (test) {
+ tester.pending(test.fullTitle().trim());
+ });
+
+ runner.on('end', function () {
+ tester.end();
+ });
+};
diff --git a/libraries/kotlin.test/js/it/js/paths.js b/libraries/kotlin.test/js/it/js/paths.js
new file mode 100644
index 00000000000..26188f99769
--- /dev/null
+++ b/libraries/kotlin.test/js/it/js/paths.js
@@ -0,0 +1,5 @@
+var paths = require('app-module-path');
+
+paths.addPath('build/classes');
+paths.addPath('build/classes/main');
+paths.addPath('build/classes/test');
diff --git a/libraries/kotlin.test/js/it/js/qunit-reporter.js b/libraries/kotlin.test/js/it/js/qunit-reporter.js
new file mode 100644
index 00000000000..1eeaef2ce2a
--- /dev/null
+++ b/libraries/kotlin.test/js/it/js/qunit-reporter.js
@@ -0,0 +1,21 @@
+var Tester = require('./test-result-checker');
+var tester = new Tester(require('./expected-outcomes'));
+
+QUnit.testDone(function (details) {
+ var testName = (details.module.replace('> ', '') + ' ' + details.name).trim();
+ if (details.skipped) {
+ tester.pending(testName);
+ }
+ else if (!details.failed) {
+ tester.passed(testName);
+ }
+ else {
+ tester.failed(testName);
+ }
+});
+
+QUnit.done(function (details) {
+ tester.printResult();
+ details.failed = tester.exitCode();
+});
+
diff --git a/libraries/kotlin.test/js/it/js/tape-plugin.js b/libraries/kotlin.test/js/it/js/tape-plugin.js
new file mode 100644
index 00000000000..c7e15a0f72c
--- /dev/null
+++ b/libraries/kotlin.test/js/it/js/tape-plugin.js
@@ -0,0 +1,36 @@
+var tape = require('tape');
+var kotlin_test = require('kotlin-test');
+
+var suiteContext = {
+ test: tape
+};
+var hasTests = false;
+
+kotlin_test.setAdapter({
+ suite: function (name, ignored, fn) {
+ suiteContext.test(name, { skip: ignored }, function(t) {
+ var prevContext = suiteContext;
+ suiteContext = t;
+ hasTests = false;
+ fn();
+ suiteContext = prevContext;
+ if (!hasTests) {
+ t.pass('fake suite assert');
+ }
+ t.end();
+ });
+ },
+
+ test: function (name, ignored, fn) {
+ hasTests = true;
+ suiteContext.test(name, { skip: ignored }, function (t) {
+ try {
+ fn();
+ } catch (e) {
+ t.ok(false, e.message);
+ }
+ t.pass('fake assert');
+ t.end();
+ });
+ }
+});
diff --git a/libraries/kotlin.test/js/it/js/tape-reporter.js b/libraries/kotlin.test/js/it/js/tape-reporter.js
new file mode 100644
index 00000000000..8adbd666dd3
--- /dev/null
+++ b/libraries/kotlin.test/js/it/js/tape-reporter.js
@@ -0,0 +1,61 @@
+var test = require('tape');
+var path = require('path');
+
+var Tester = require('./test-result-checker');
+
+// Tape doesn't report pending tests.
+// See https://github.com/substack/tape/pull/197 and https://github.com/substack/tape/issues/90
+var full = require('./expected-outcomes');
+var noPending = {};
+for (var name in full) {
+ var result = full[name];
+ if (result !== 'pending') {
+ noPending[name] = result;
+ }
+}
+
+var tester = new Tester(noPending);
+
+process.on('exit', function () {
+ tester.end();
+});
+
+var stream = test.createStream({objectMode: true});
+
+var nameStack = [];
+var passed;
+var shouldSkip;
+
+stream.on('data', function (row) {
+ if (row.type === 'test') {
+ var name = row.name === '(anonymous)' ? ' ' : row.name;
+ nameStack.push(name);
+ passed = true;
+ shouldSkip = false;
+ }
+ else if (row.type === 'end') {
+ if (!shouldSkip) {
+ var name = nameStack.join(' ').trim();
+ if (passed) {
+ tester.passed(name);
+ }
+ else {
+ tester.failed(name);
+ }
+ }
+ shouldSkip = true;
+ nameStack.pop();
+ }
+ else if (row.type === 'assert') {
+ if (nameStack.length < 2 || row.name === 'fake suite assert') {
+ shouldSkip = true;
+ }
+ if (!row.ok) {
+ passed = false;
+ }
+ }
+});
+
+process.argv.slice(2).forEach(function (file) {
+ require(path.resolve(file));
+});
diff --git a/libraries/kotlin.test/js/it/js/test-result-checker.js b/libraries/kotlin.test/js/it/js/test-result-checker.js
new file mode 100644
index 00000000000..6449639dbbd
--- /dev/null
+++ b/libraries/kotlin.test/js/it/js/test-result-checker.js
@@ -0,0 +1,63 @@
+var Tester = function(testMap) {
+ this._testMap = testMap;
+
+ this._testCount = {};
+
+ this._passed = 0;
+ this._total = Object.keys(testMap).length;
+
+ this._errors = []
+};
+
+Tester.prototype._check = function(name, result) {
+ var count = this._testCount[name] | 0;
+ this._testCount = count + 1;
+ if (count === 1) {
+ this._errors.push('Duplicate test: "' + name + '"');
+ return;
+ }
+
+ var expected = this._testMap[name];
+ if (!expected) {
+ this._errors.push('Unexpected test: "' + name + '"');
+ return;
+ }
+
+ if (result !== expected) {
+ this._errors.push('Unexpected test: "' + name + '"');
+ return;
+ }
+
+ this._passed++;
+};
+
+Tester.prototype.passed = function(name) {
+ this._check(name, 'pass');
+};
+
+Tester.prototype.failed = function(name) {
+ this._check(name, 'fail');
+};
+
+Tester.prototype.pending = function(name) {
+ this._check(name, 'pending');
+};
+
+Tester.prototype.printResult = function() {
+ console.log("Passed " + this._passed + " out of " + this._total);
+ for (var i = 0; i < this._errors.length; ++i) {
+ console.log(this._errors[i]);
+ }
+};
+
+Tester.prototype.exitCode = function() {
+ return this._errors.length;
+};
+
+Tester.prototype.end = function() {
+ this.printResult();
+ process.exitCode = this.exitCode();
+ process.exit();
+};
+
+module.exports = Tester;
\ No newline at end of file
diff --git a/libraries/kotlin.test/js/it/package.json b/libraries/kotlin.test/js/it/package.json
new file mode 100644
index 00000000000..72d16ca1641
--- /dev/null
+++ b/libraries/kotlin.test/js/it/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "it",
+ "version": "1.0.0",
+ "description": "",
+ "main": "js/index.js",
+ "scripts": {
+ "test-jasmine": "jasmine js/paths.js js/jasmine-reporter.js build/classes/test/kotlin-test-js-it_test.js",
+ "test-jest": "jest",
+ "test-mocha": "mocha -r js/paths.js --reporter js/mocha-reporter.js build/classes/test/kotlin-test-js-it_test.js",
+ "test-qunit": "qunit -c js/paths.js -d js/qunit-reporter.js -t build/classes/test/kotlin-test-js-it_test.js",
+ "test-tape": "tape js/paths.js js/tape-reporter.js js/tape-plugin.js build/classes/test/kotlin-test-js-it_test.js"
+ },
+ "author": "",
+ "license": "ISC",
+ "devDependencies": {
+ "app-module-path": "^2.2.0",
+ "jasmine": "^2.6.0",
+ "jest": "^20.0.4",
+ "mocha": "^3.4.2",
+ "qunit": "^1.0.0",
+ "tape": "^4.6.3",
+ "watchify": "^3.9.0"
+ },
+ "jest": {
+ "verbose": true,
+ "roots": [
+ "/build/classes/",
+ "/build/classes/main/",
+ "/build/classes/test/"
+ ],
+ "testResultsProcessor": "/js/jest-reporter.js",
+ "testRegex": "_test\\.js$",
+ "moduleNameMapper": {
+ "^kotlin$": "/build/classes/kotlin.js",
+ "^kotlin-test$": "/build/classes/kotlin-test.js",
+ "^kotlin-test-js-it_main$": "/build/classes/main/kotlin-test-js-it_main.js"
+ }
+ }
+}
diff --git a/libraries/kotlin.test/js/it/src/Main.kt b/libraries/kotlin.test/js/it/src/Main.kt
new file mode 100644
index 00000000000..1b2fec59b64
--- /dev/null
+++ b/libraries/kotlin.test/js/it/src/Main.kt
@@ -0,0 +1 @@
+fun foo() = 10
\ No newline at end of file
diff --git a/libraries/kotlin.test/js/it/test/MainTest.kt b/libraries/kotlin.test/js/it/test/MainTest.kt
new file mode 100644
index 00000000000..5341fc70563
--- /dev/null
+++ b/libraries/kotlin.test/js/it/test/MainTest.kt
@@ -0,0 +1,23 @@
+import kotlin.test.*
+
+class SimpleTest {
+
+ @Test fun testFoo() {
+ assertEquals(20, foo())
+ }
+
+ @Test fun testBar() {
+ assertEquals(10, foo())
+ }
+
+ @Ignore @Test fun testFooWrong() {
+ assertEquals(20, foo())
+ }
+
+}
+
+@Ignore
+class TestTest {
+ @Test fun emptyTest() {
+ }
+}
\ No newline at end of file
diff --git a/libraries/kotlin.test/js/it/test/org/OrgTest.kt b/libraries/kotlin.test/js/it/test/org/OrgTest.kt
new file mode 100644
index 00000000000..84c114f0a8f
--- /dev/null
+++ b/libraries/kotlin.test/js/it/test/org/OrgTest.kt
@@ -0,0 +1,8 @@
+package org
+
+import kotlin.test.*
+
+class OrgTest {
+ @Test fun test() {
+ }
+}
diff --git a/libraries/kotlin.test/js/it/test/org/other/name/NameTest.kt b/libraries/kotlin.test/js/it/test/org/other/name/NameTest.kt
new file mode 100644
index 00000000000..a06b27765f1
--- /dev/null
+++ b/libraries/kotlin.test/js/it/test/org/other/name/NameTest.kt
@@ -0,0 +1,8 @@
+package org.other.name
+
+import kotlin.test.*
+
+class NameTest {
+ @Test fun nameIsOk() {
+ }
+}
\ No newline at end of file
diff --git a/libraries/kotlin.test/js/it/test/org/some/SomeTest.kt b/libraries/kotlin.test/js/it/test/org/some/SomeTest.kt
new file mode 100644
index 00000000000..d90d12f9e9e
--- /dev/null
+++ b/libraries/kotlin.test/js/it/test/org/some/SomeTest.kt
@@ -0,0 +1,8 @@
+package org.some
+
+import kotlin.test.*
+
+class SomeTest {
+ @Test fun someIsBetterThanNothing() {
+ }
+}
diff --git a/libraries/kotlin.test/js/it/test/org/some/name/NameTest.kt b/libraries/kotlin.test/js/it/test/org/some/name/NameTest.kt
new file mode 100644
index 00000000000..de4a1626bc9
--- /dev/null
+++ b/libraries/kotlin.test/js/it/test/org/some/name/NameTest.kt
@@ -0,0 +1,8 @@
+package org.some.name
+
+import kotlin.test.*
+
+class NameTest {
+ @Test fun test() {
+ }
+}
diff --git a/libraries/kotlin.test/js/src/main/kotlin/JsTestApi.kt b/libraries/kotlin.test/js/src/main/kotlin/JsTestApi.kt
new file mode 100644
index 00000000000..091108be4bc
--- /dev/null
+++ b/libraries/kotlin.test/js/src/main/kotlin/JsTestApi.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+import kotlin.test.*
+
+/**
+ * Functions in this file are exposed in the root package to simplify their use from JavaScript.
+ * For example: require('kotlin-test').setAdapter({ /* Your custom [FrameworkAdapter] here */ });
+ */
+
+/**
+ * Overrides current framework adapter with a provided instance of [FrameworkAdapter]. Use in order to support custom test frameworks.
+ *
+ * Also some string arguments are supported. Use "qunit" to set the adapter to [QUnit](https://qunitjs.com/), "mocha" for
+ * [Mocha](https://mochajs.org/), "jest" for [Jest](https://facebook.github.io/jest/),
+ * "jasmine" for [Jasmine](https://github.com/jasmine/jasmine), and "auto" to detect one of those frameworks automatically.
+ *
+ * If this function is not called, the test framework will be detected automatically (as if "auto" was passed).
+ *
+ */
+@JsName("setAdapter")
+internal fun setAdapter(adapter: dynamic) = kotlin.test.setAdapter(adapter)
\ No newline at end of file
diff --git a/libraries/kotlin.test/js/src/main/kotlin/QUnit.kt b/libraries/kotlin.test/js/src/main/kotlin/QUnit.kt
deleted file mode 100644
index d6a1637d876..00000000000
--- a/libraries/kotlin.test/js/src/main/kotlin/QUnit.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package QUnit
-
-/**
- * The [QUnit](http://qunitjs.com/) API
- */
-
-external fun ok(actual: Boolean, message: String?): Unit
-
diff --git a/libraries/kotlin.test/js/src/main/kotlin/junitTest.kt b/libraries/kotlin.test/js/src/main/kotlin/junitTest.kt
deleted file mode 100644
index 952dc742f5a..00000000000
--- a/libraries/kotlin.test/js/src/main/kotlin/junitTest.kt
+++ /dev/null
@@ -1,4 +0,0 @@
-package org.junit
-
-@Suppress("IMPLEMENTATION_WITHOUT_HEADER")
-impl annotation class Test(val name: String = "")
diff --git a/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/Annotations.kt b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/Annotations.kt
new file mode 100644
index 00000000000..10f8f229fe8
--- /dev/null
+++ b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/Annotations.kt
@@ -0,0 +1,27 @@
+/*
+ * 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
+
+/**
+ * Marks a function as a test.
+ */
+public annotation class Test
+
+/**
+ * Marks a test or a suite as ignored/pending.
+ */
+public annotation class Ignore
\ No newline at end of file
diff --git a/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt
new file mode 100644
index 00000000000..f5e165f5065
--- /dev/null
+++ b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt
@@ -0,0 +1,97 @@
+/*
+ * 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
+
+/**
+ * Describes the result of an assertion execution.
+ */
+public external interface AssertionResult {
+ val result: Boolean
+ val expected: Any?
+ val actual: Any?
+ val lazyMessage: () -> String?
+}
+
+internal var assertHook: (AssertionResult) -> Unit = { _ -> }
+
+internal object DefaultJsAsserter : Asserter {
+ private var e: Any? = undefined
+ private var a: Any? = undefined
+
+ override fun assertEquals(message: String?, expected: Any?, actual: Any?) {
+ e = expected
+ a = actual
+ super.assertEquals(message, expected, actual)
+ }
+
+ override fun assertNotEquals(message: String?, illegal: Any?, actual: Any?) {
+ e = illegal
+ a = actual
+ super.assertNotEquals(message, illegal, actual)
+ }
+
+ override fun assertNull(message: String?, actual: Any?) {
+ a = actual
+ super.assertNull(message, actual)
+ }
+
+ override fun assertNotNull(message: String?, actual: Any?) {
+ a = actual
+ super.assertNotNull(message, actual)
+ }
+
+ override fun assertTrue(lazyMessage: () -> String?, actual: Boolean) {
+ if (!actual) {
+ failWithMessage(lazyMessage)
+ }
+ else {
+ invokeHook(true, lazyMessage)
+ }
+ }
+
+ override fun assertTrue(message: String?, actual: Boolean) {
+ assertTrue({ message }, actual)
+ }
+
+ override fun fail(message: String?): Nothing {
+ failWithMessage { message }
+ }
+
+ private fun failWithMessage(lazyMessage: () -> String?): Nothing {
+ val message = lazyMessage()
+ invokeHook(false) { message }
+ if (message == null)
+ throw AssertionError()
+ else
+ throw AssertionError(message)
+ }
+
+ private fun invokeHook(result: Boolean, lazyMessage: () -> String?) {
+ try {
+ assertHook(object : AssertionResult {
+ override val result: Boolean = result
+ override val expected: Any? = e
+ override val actual: Any? = a
+ override val lazyMessage: () -> String? = lazyMessage
+ })
+ }
+ finally {
+ e = undefined
+ a = undefined
+ }
+ }
+}
\ No newline at end of file
diff --git a/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/FrameworkAdapter.kt b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/FrameworkAdapter.kt
new file mode 100644
index 00000000000..ede1fd1a65e
--- /dev/null
+++ b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/FrameworkAdapter.kt
@@ -0,0 +1,53 @@
+/*
+ * 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
+
+/**
+ * Serves as a bridge to a testing framework.
+ *
+ * The tests structure is defined using internal functions suite and test, which delegate to correspoding functions of a [FrameworkAdapter].
+ * Sample test layout:
+ *
+ * suite('a suite', false, function() {
+ * suite('a subsuite', false, function() {
+ * test('a test', false, function() {...});
+ * test('an ignored/pending test', true, function() {...});
+ * });
+ * suite('an ignored/pending test', true, function() {...});
+ * });
+ *
+ */
+public external interface FrameworkAdapter {
+
+ /**
+ * Declares a test suite.
+ *
+ * @param name the name of the test suite, e.g. a class name
+ * @param ignored whether the test suite is ignored, e.g. marked with [Ignore] annotation
+ * @param suiteFn defines nested suites by calling [kotlin.test.suite] and tests by calling [kotlin.test.test]
+ */
+ fun suite(name: String, ignored: Boolean, suiteFn: () -> Unit)
+
+ /**
+ * Declares a test.
+ *
+ * @param name the test name.
+ * @param ignored whether the test is ignored
+ * @param testFn contains test body invocation
+ */
+ fun test(name: String, ignored: Boolean, testFn: () -> Unit)
+}
diff --git a/libraries/kotlin.test/js/src/main/kotlin/JsImpl.kt b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/JsImpl.kt
similarity index 62%
rename from libraries/kotlin.test/js/src/main/kotlin/JsImpl.kt
rename to libraries/kotlin.test/js/src/main/kotlin/kotlin/test/JsImpl.kt
index 7aa8c834349..d9c15b97081 100644
--- a/libraries/kotlin.test/js/src/main/kotlin/JsImpl.kt
+++ b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/JsImpl.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2010-2016 JetBrains s.r.o.
+ * 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.
@@ -40,33 +40,6 @@ impl fun assertFailsWith(exceptionClass: KClass, message: Str
/**
- * Provides the JS implementation of asserter using [QUnit](http://QUnitjs.com/)
+ * Provides the JS implementation of asserter
*/
-internal impl fun lookupAsserter(): Asserter = qunitAsserter
-
-private val qunitAsserter = QUnitAsserter()
-
-// TODO: make object in 1.2
-class QUnitAsserter : Asserter {
-
- override fun assertTrue(lazyMessage: () -> String?, actual: Boolean) {
- assertTrue(actual, lazyMessage())
- }
-
- override fun assertTrue(message: String?, actual: Boolean) {
- QUnit.ok(actual, message)
- if (!actual) failWithMessage(message)
- }
-
- override fun fail(message: String?): Nothing {
- QUnit.ok(false, message)
- failWithMessage(message)
- }
-
- private fun failWithMessage(message: String?): Nothing {
- if (message == null)
- throw AssertionError()
- else
- throw AssertionError(message)
- }
-}
+internal impl fun lookupAsserter(): Asserter = DefaultJsAsserter
\ No newline at end of file
diff --git a/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/TestApi.kt b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/TestApi.kt
new file mode 100644
index 00000000000..ff91735473b
--- /dev/null
+++ b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/TestApi.kt
@@ -0,0 +1,92 @@
+/*
+ * 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
+
+import kotlin.test.adapters.*
+
+/**
+ * Overrides current framework adapter with a provided instance of [FrameworkAdapter]. Use in order to support custom test frameworks.
+ *
+ * Also some string arguments are supported. Use "qunit" to set the adapter to [QUnit](https://qunitjs.com/), "mocha" for
+ * [Mocha](https://mochajs.org/), "jest" for [Jest](https://facebook.github.io/jest/),
+ * "jasmine" for [Jasmine](https://github.com/jasmine/jasmine), and "auto" to detect one of those frameworks automatically.
+ *
+ * If this function is not called, the test framework will be detected automatically (as if "auto" was passed).
+ *
+ */
+internal fun setAdapter(adapter: dynamic) {
+ if (js("typeof adapter === 'string'")) {
+ NAME_TO_ADAPTER[adapter]?.let {
+ setAdapter(it.invoke())
+ }?: throw IllegalArgumentException("Unsupported test framework adapter: '$adapter'")
+ }
+ else {
+ currentAdapter = adapter
+ }
+}
+
+/**
+ * Use in order to define which action should be taken by the test framework on the [AssertionResult].
+ */
+internal fun setAssertHook(hook: (AssertionResult) -> Unit) {
+ assertHook = hook
+}
+
+
+/**
+ * The functions below are used by the compiler to describe the tests structure, e.g.
+ *
+ * suite('a suite', false, function() {
+ * suite('a subsuite', false, function() {
+ * test('a test', false, function() {...});
+ * test('an ignored/pending test', true, function() {...});
+ * });
+ * suite('an ignored/pending test', true, function() {...});
+ * });
+ */
+
+@JsName("suite")
+internal fun suite(name: String, ignored: Boolean, suiteFn: () -> Unit) {
+ adapter().suite(name, ignored, suiteFn)
+}
+
+@JsName("test")
+internal fun test(name: String, ignored: Boolean, testFn: () -> Unit) {
+ adapter().test(name, ignored, testFn)
+}
+
+internal var currentAdapter: FrameworkAdapter? = null
+
+internal fun adapter(): FrameworkAdapter {
+ val result = currentAdapter ?: detectAdapter()
+ currentAdapter = result
+ return result
+}
+
+
+internal fun detectAdapter() = when {
+ isQUnit() -> QUnitAdapter()
+ isJasmine() -> JasmineLikeAdapter()
+ else -> BareAdapter()
+}
+
+internal val NAME_TO_ADAPTER: Map FrameworkAdapter> = mapOf(
+ "qunit" to ::QUnitAdapter,
+ "jasmine" to ::JasmineLikeAdapter,
+ "mocha" to ::JasmineLikeAdapter,
+ "jest" to ::JasmineLikeAdapter,
+ "auto" to ::detectAdapter)
\ No newline at end of file
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
new file mode 100644
index 00000000000..a53a716a11b
--- /dev/null
+++ b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/BareAdapter.kt
@@ -0,0 +1,37 @@
+/*
+ * 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
+
+import kotlin.test.FrameworkAdapter
+
+/**
+ * A fallback adapter for the case when no framework is detected.
+ */
+internal open class BareAdapter : FrameworkAdapter {
+
+ override fun suite(name: String, ignored: Boolean, suiteFn: () -> Unit) {
+ if (!ignored) {
+ suiteFn()
+ }
+ }
+
+ override fun test(name: String, ignored: Boolean, testFn: () -> Unit) {
+ if (!ignored) {
+ testFn()
+ }
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 00000000000..703233d7945
--- /dev/null
+++ b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/Externals.kt
@@ -0,0 +1,38 @@
+/*
+ * 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
+
+/**
+ * The [QUnit](http://qunitjs.com/) API
+ */
+internal external object QUnit {
+ fun module(name: String, suiteFn: () -> Unit): Unit
+ fun test(name: String, testFn: (dynamic) -> Unit): Unit
+ fun skip(name: String, testFn: (dynamic) -> Unit): Unit
+}
+
+/**
+ * Jasmine/Mocha/Jest API
+ */
+internal external fun describe(name: String, fn: () -> Unit)
+internal external fun xdescribe(name: String, fn: () -> Unit)
+internal external fun it(name: String, fn: () -> Unit)
+internal external fun xit(name: String, fn: () -> Unit)
+
+internal fun isQUnit() = jsTypeOf(QUnit) !== "undefined"
+
+internal fun isJasmine() = js("typeof describe === 'function' && typeof it === 'function'")
\ No newline at end of file
diff --git a/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/JasmineLikeAdapter.kt b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/JasmineLikeAdapter.kt
new file mode 100644
index 00000000000..b916e772ab2
--- /dev/null
+++ b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/JasmineLikeAdapter.kt
@@ -0,0 +1,43 @@
+/*
+ * 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
+
+import kotlin.test.FrameworkAdapter
+
+/**
+ * [Jasmine](https://github.com/jasmine/jasmine) adapter.
+ * Also used for [Mocha](https://mochajs.org/) and [Jest](https://facebook.github.io/jest/).
+ */
+internal class JasmineLikeAdapter : FrameworkAdapter {
+ override fun suite(name: String, ignored: Boolean, suiteFn: () -> Unit) {
+ if (ignored) {
+ xdescribe(name, suiteFn)
+ }
+ else {
+ describe(name, suiteFn)
+ }
+ }
+
+ override fun test(name: String, ignored: Boolean, testFn: () -> Unit) {
+ if (ignored) {
+ xit(name, testFn)
+ }
+ else {
+ it(name, testFn)
+ }
+ }
+}
\ No newline at end of file
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/QUnitAdapter.kt
new file mode 100644
index 00000000000..749737ba358
--- /dev/null
+++ b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/adapters/QUnitAdapter.kt
@@ -0,0 +1,56 @@
+/*
+ * 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
+
+import kotlin.test.FrameworkAdapter
+import kotlin.test.assertHook
+import kotlin.test.assertTrue
+
+/**
+ * [QUnit](http://qunitjs.com/) adapter
+ */
+internal class QUnitAdapter : FrameworkAdapter {
+ var ignoredSuite = false;
+
+ override fun suite(name: String, ignored: Boolean, suiteFn: () -> Unit) {
+ val prevIgnore = ignoredSuite
+ ignoredSuite = ignoredSuite or ignored
+ QUnit.module(name, suiteFn)
+ ignoredSuite = prevIgnore
+ }
+
+ override fun test(name: String, ignored: Boolean, testFn: () -> Unit) {
+ if (ignored or ignoredSuite) {
+ QUnit.skip(name, wrapTest(testFn))
+ }
+ else {
+ QUnit.test(name, wrapTest(testFn))
+ }
+ }
+
+ private fun wrapTest(testFn: () -> Unit): (dynamic) -> Unit = { assert ->
+ var assertionsHappened = false
+ assertHook = { testResult ->
+ assertionsHappened = true
+ assert.ok(testResult.result, testResult.lazyMessage())
+ }
+ testFn()
+ if (!assertionsHappened) {
+ assertTrue(true, "A test with no assertions is considered successful")
+ }
+ }
+}
\ No newline at end of file
diff --git a/libraries/kotlin.test/js/src/main/kotlin/org/junit/junitTest.kt b/libraries/kotlin.test/js/src/main/kotlin/org/junit/junitTest.kt
new file mode 100644
index 00000000000..e0df862fa6b
--- /dev/null
+++ b/libraries/kotlin.test/js/src/main/kotlin/org/junit/junitTest.kt
@@ -0,0 +1,6 @@
+package org.junit
+
+@Deprecated("Use 'Test' from kotlin.test package",
+ replaceWith = ReplaceWith("Test", "kotlin.test.Test"),
+ level = DeprecationLevel.WARNING)
+typealias Test = kotlin.test.Test
diff --git a/libraries/settings.gradle b/libraries/settings.gradle
index c1aa9272efe..31baae8cf5e 100644
--- a/libraries/settings.gradle
+++ b/libraries/settings.gradle
@@ -6,6 +6,7 @@ include ':kotlin-test:kotlin-test-common'
include ':kotlin-test:kotlin-test-jvm'
include ':kotlin-test:kotlin-test-junit'
include ':kotlin-test:kotlin-test-js'
+include ':kotlin-test:kotlin-test-js:kotlin-test-js-it'
include ':kotlin-stdlib-common'
include ':kotlin-stdlib'
include ':kotlin-stdlib-js'
@@ -58,6 +59,7 @@ project(':kotlin-test:kotlin-test-common').projectDir = "$rootDir/kotlin.test/co
project(':kotlin-test:kotlin-test-jvm').projectDir = "$rootDir/kotlin.test/jvm" as File
project(':kotlin-test:kotlin-test-junit').projectDir = "$rootDir/kotlin.test/junit" as File
project(':kotlin-test:kotlin-test-js').projectDir = "$rootDir/kotlin.test/js" as File
+project(':kotlin-test:kotlin-test-js:kotlin-test-js-it').projectDir = "$rootDir/kotlin.test/js/it" as File
project(':kotlin-stdlib-common').projectDir = "$rootDir/stdlib/common" as File
project(':kotlin-stdlib').projectDir = "$rootDir/stdlib" as File
project(':kotlin-stdlib-js').projectDir = "$rootDir/stdlib/js" as File