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