diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/JSTestGenerator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/JSTestGenerator.kt index ea1faa5b962..a97c2245c65 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/JSTestGenerator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/JSTestGenerator.kt @@ -62,9 +62,16 @@ class JSTestGenerator(val context: TranslationContext) { 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) + val descriptors = classDescriptor.unsubstitutedMemberScope + .getContributedDescriptors(DescriptorKindFilter.FUNCTIONS, MemberScope.ALL_NAME_FILTER) + .filterIsInstance() + + val beforeFunctions = descriptors.filter { it.isBefore } + val afterFunctions = descriptors.filter { it.isAfter } + + descriptors.forEach { + if (it.isTest) { + generateCodeForTestMethod(it, beforeFunctions, afterFunctions, classDescriptor, suiteFunction) } } @@ -75,19 +82,39 @@ class JSTestGenerator(val context: TranslationContext) { } } - private fun generateCodeForTestMethod(functionDescriptor: FunctionDescriptor, classDescriptor: ClassDescriptor, parentFun: JsFunction) { - val functionToTest = generateTestFunction(functionDescriptor, classDescriptor, parentFun.scope) + private fun generateCodeForTestMethod(functionDescriptor: FunctionDescriptor, + beforeDescriptors: List, + afterDescriptors: List, + classDescriptor: ClassDescriptor, + parentFun: JsFunction) { + val functionToTest = generateTestFunction(functionDescriptor, beforeDescriptors, afterDescriptors, 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) + private fun generateTestFunction(functionDescriptor: FunctionDescriptor, + beforeDescriptors: List, + afterDescriptors: List, + classDescriptor: ClassDescriptor, + scope: JsScope): JsFunction { + val functionToTest = JsFunction(scope, JsBlock(), "test function") + val innerContext = context.contextWithScope(functionToTest) + + val expression = ReferenceTranslator.translateAsTypeReference(classDescriptor, innerContext) val testClass = JsNew(expression) - val functionToTestCall = CallTranslator.buildCall(context, functionDescriptor, emptyList(), testClass) - val functionToTest = JsFunction(scope, "test function") - functionToTest.body = JsBlock(functionToTestCall.makeStmt()) + val classVal = innerContext.defineTemporary(testClass) + + fun FunctionDescriptor.buildCall() = CallTranslator.buildCall(context, this, emptyList(), classVal).makeStmt() + + functionToTest.body.statements += beforeDescriptors.map { it.buildCall() } + + functionToTest.body.statements += if (afterDescriptors.isEmpty()) { + functionDescriptor.buildCall() + } + else { + JsTry(JsBlock(functionDescriptor.buildCall()), listOf(), JsBlock(afterDescriptors.map { it.buildCall() })) + } return functionToTest } @@ -108,6 +135,12 @@ class JSTestGenerator(val context: TranslationContext) { private val DeclarationDescriptor.isIgnored get() = annotationFinder("Ignore", "kotlin.test") + private val FunctionDescriptor.isBefore + get() = annotationFinder("Before", "kotlin.test") + + private val FunctionDescriptor.isAfter + get() = annotationFinder("After", "kotlin.test") + private fun DeclarationDescriptor.annotationFinder(shortName: String, vararg packages: String) = packages.any { packageName -> annotations.hasAnnotation(FqName("$packageName.$shortName")) } diff --git a/libraries/kotlin.test/js/it/src/test/kotlin/MainTest.kt b/libraries/kotlin.test/js/it/src/test/kotlin/MainTest.kt index 5341fc70563..a862a3c23d2 100644 --- a/libraries/kotlin.test/js/it/src/test/kotlin/MainTest.kt +++ b/libraries/kotlin.test/js/it/src/test/kotlin/MainTest.kt @@ -1,13 +1,24 @@ import kotlin.test.* + +var value = 5 + class SimpleTest { + @Before fun beforeFun() { + value *= 2 + } + + @After fun afterFun() { + value /= 2 + } + @Test fun testFoo() { - assertEquals(20, foo()) + assertNotEquals(value, foo()) } @Test fun testBar() { - assertEquals(10, foo()) + assertEquals(value, foo()) } @Ignore @Test fun testFooWrong() { 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 index 10f8f229fe8..7eda3a9c37a 100644 --- a/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/Annotations.kt +++ b/libraries/kotlin.test/js/src/main/kotlin/kotlin/test/Annotations.kt @@ -24,4 +24,14 @@ public annotation class Test /** * Marks a test or a suite as ignored/pending. */ -public annotation class Ignore \ No newline at end of file +public annotation class Ignore + +/** + * Marks a function to be invoked before each test. + */ +public annotation class Before + +/** + * Marks a function to be invoked after each test. + */ +public annotation class After \ No newline at end of file