Unfinished business
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
deleted file mode 100644
index 797acea..0000000
--- a/.idea/runConfigurations.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="RunConfigurationProducerService">
-    <option name="ignoredProducers">
-      <set>
-        <option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
-      </set>
-    </option>
-  </component>
-</project>
\ No newline at end of file
diff --git a/libraries/kotlin.test/build.gradle.kts b/libraries/kotlin.test/build.gradle.kts
index fc76598..f01f1aa 100644
--- a/libraries/kotlin.test/build.gradle.kts
+++ b/libraries/kotlin.test/build.gradle.kts
@@ -55,6 +55,24 @@
     extendsFrom(jsApiVariant)
 }
 
+val wasmApiVariant by configurations.creating {
+    isCanBeConsumed = true
+    isCanBeResolved = false
+    attributes {
+        attribute(Usage.USAGE_ATTRIBUTE, objects.named("kotlin-api"))
+        attribute(KotlinPlatformType.attribute, KotlinPlatformType.js)
+    }
+}
+val wasmRuntimeVariant by configurations.creating {
+    isCanBeConsumed = true
+    isCanBeResolved = false
+    attributes {
+        attribute(Usage.USAGE_ATTRIBUTE, objects.named("kotlin-runtime"))
+        attribute(KotlinPlatformType.attribute, KotlinPlatformType.js)
+    }
+    extendsFrom(wasmApiVariant)
+}
+
 val nativeApiVariant by configurations.creating {
     isCanBeConsumed = true
     isCanBeResolved = false
@@ -76,6 +94,7 @@
 dependencies {
     jvmApi(project(":kotlin-stdlib"))
     jsApiVariant("$group:kotlin-test-js:$version")
+    wasmApiVariant("$group:kotlin-test-wasm:$version")
     commonVariant("$group:kotlin-test-common:$version")
     commonVariant("$group:kotlin-test-annotations-common:$version")
 }
@@ -114,6 +133,8 @@
     }
     addVariantsFromConfiguration(jsApiVariant) { mapToOptional() }
     addVariantsFromConfiguration(jsRuntimeVariant) { mapToOptional() }
+    addVariantsFromConfiguration(wasmApiVariant) { mapToOptional() }
+    addVariantsFromConfiguration(wasmRuntimeVariant) { mapToOptional() }
     addVariantsFromConfiguration(nativeApiVariant) { mapToOptional() }
     addVariantsFromConfiguration(commonVariant) { mapToOptional() }
 }
@@ -236,6 +257,38 @@
     }
 }
 
+val (wasmApi, wasmRuntime) = listOf("api", "runtime").map { usage ->
+    configurations.create("wasm${usage.capitalize()}") {
+        isCanBeConsumed = true
+        isCanBeResolved = false
+        attributes {
+            attribute(Usage.USAGE_ATTRIBUTE, objects.named("kotlin-$usage"))
+            attribute(KotlinPlatformType.attribute, KotlinPlatformType.js)
+        }
+    }
+}
+wasmRuntime.extendsFrom(wasmApi)
+
+dependencies {
+    wasmApi(project(":kotlin-stdlib-wasm"))
+}
+
+//artifacts {
+//    val wasmJar = tasks.getByPath(":kotlin-test:kotlin-test-wasm:jsJar")
+//    add(wasmApi.name, wasmJar)
+//    add(wasmRuntime.name, wasmJar)
+//}
+
+val wasmComponent = componentFactory.adhoc("wasm").apply {
+    addVariantsFromConfiguration(wasmApi) {
+        mapToMavenScope("compile")
+    }
+    addVariantsFromConfiguration(wasmRuntime) {
+        mapToMavenScope("runtime")
+    }
+}
+
+
 val commonMetadata by configurations.creating {
     isCanBeConsumed = true
     isCanBeResolved = false
@@ -306,6 +359,12 @@
             artifact(tasks.getByPath(":kotlin-test:kotlin-test-js:sourcesJar") as Jar)
             configureKotlinPomAttributes(project, "Kotlin Test for JS")
         }
+        create("wasm", MavenPublication::class) {
+            artifactId = "kotlin-test-wasm"
+            from(wasmComponent)
+            //artifact(tasks.getByPath(":kotlin-test:kotlin-test-wasm:sourcesJar") as Jar)
+            configureKotlinPomAttributes(project, "Kotlin Test for WASM")
+        }
         create("common", MavenPublication::class) {
             artifactId = "kotlin-test-common"
             from(commonMetadataComponent)
diff --git a/libraries/kotlin.test/js/it/js/jasmine-reporter.js b/libraries/kotlin.test/js/it/js/jasmine-reporter.js
index 8f43b69..ab95434 100644
--- a/libraries/kotlin.test/js/it/js/jasmine-reporter.js
+++ b/libraries/kotlin.test/js/it/js/jasmine-reporter.js
@@ -19,4 +19,4 @@
             tester.pending(name);
         }
     }
-});
\ No newline at end of file
+})
\ No newline at end of file
diff --git a/libraries/kotlin.test/wasm/build.gradle.kt b/libraries/kotlin.test/wasm/build.gradle.kt
new file mode 100644
index 0000000..9faba69
--- /dev/null
+++ b/libraries/kotlin.test/wasm/build.gradle.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
+
+plugins {
+    kotlin("multiplatform")
+}
+
+val commonMainSources by task<Sync> {
+    from(
+        "$rootDir/libraries/kotlin.test/common/src",
+        "$rootDir/libraries/kotlin.test/annotations-common/src"
+    )
+    into("$buildDir/commonMainSources")
+}
+
+//val commonTestSources by task<Sync> {
+//    from("$rootDir/libraries/kotlin.test/common/src/test/kotlin")
+//    into("$buildDir/commonTestSources")
+//}
+
+val wasmMainSources by task<Sync> {
+    from("$rootDir/libraries/kotlin.test/wasm/src")
+    into("$buildDir/wasmMainSources")
+}
+
+kotlin {
+    js(IR) {
+        nodejs()
+    }
+
+    sourceSets {
+        val commonMain by getting {
+            dependencies {
+                api(project(":kotlin-stdlib-wasm"))
+            }
+            kotlin.srcDir(commonMainSources.get().destinationDir)
+        }
+//        val commonTest by getting {
+//            kotlin.srcDir(commonTestSources.get().destinationDir)
+//        }
+        val jsMain by getting {
+            dependencies {
+                api(project(":kotlin-stdlib-wasm"))
+            }
+            kotlin.srcDir(wasmMainSources.get().destinationDir)
+        }
+    }
+}
+
+tasks.withType<KotlinCompile<*>>().configureEach {
+    kotlinOptions.freeCompilerArgs += listOf(
+        "-Xallow-kotlin-package",
+        "-opt-in=kotlin.ExperimentalMultiplatform",
+        "-opt-in=kotlin.contracts.ExperimentalContracts"
+    )
+}
+
+tasks.named("compileKotlinJs") {
+    (this as KotlinCompile<*>).kotlinOptions.freeCompilerArgs += "-Xir-module-name=kotlin-test"
+    dependsOn(commonMainSources)
+    dependsOn(wasmMainSources)
+}
+
+//tasks.named("compileTestKotlinJs") {
+//    dependsOn(commonTestSources)
+//}
diff --git a/libraries/kotlin.test/wasm/src/main/kotlin/kotlin/test/Annotations.kt b/libraries/kotlin.test/wasm/src/main/kotlin/kotlin/test/Annotations.kt
new file mode 100644
index 0000000..cf4cc2bd
--- /dev/null
+++ b/libraries/kotlin.test/wasm/src/main/kotlin/kotlin/test/Annotations.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package kotlin.test
+
+/**
+ * Marks a function as a test.
+ */
+@Target(AnnotationTarget.FUNCTION)
+public actual annotation class Test
+
+/**
+ * Marks a test or a suite as ignored.
+ */
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
+public actual annotation class Ignore
+
+/**
+ * Marks a function to be invoked before each test.
+ */
+@Target(AnnotationTarget.FUNCTION)
+public actual annotation class BeforeTest
+
+/**
+ * Marks a function to be invoked after each test.
+ */
+@Target(AnnotationTarget.FUNCTION)
+public actual annotation class AfterTest
diff --git a/libraries/kotlin.test/wasm/src/main/kotlin/kotlin/test/BareAdapter.kt b/libraries/kotlin.test/wasm/src/main/kotlin/kotlin/test/BareAdapter.kt
new file mode 100644
index 0000000..ac2cc74
--- /dev/null
+++ b/libraries/kotlin.test/wasm/src/main/kotlin/kotlin/test/BareAdapter.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+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: () -> Any?) {
+        if (!ignored) {
+            testFn()
+        }
+    }
+}
diff --git a/libraries/kotlin.test/wasm/src/main/kotlin/kotlin/test/DefaultWasmAsserter.kt b/libraries/kotlin.test/wasm/src/main/kotlin/kotlin/test/DefaultWasmAsserter.kt
new file mode 100644
index 0000000..14d4282
--- /dev/null
+++ b/libraries/kotlin.test/wasm/src/main/kotlin/kotlin/test/DefaultWasmAsserter.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+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 assertSame(message: String?, expected: Any?, actual: Any?) {
+        e = expected
+        a = actual
+        super.assertSame(message, expected, actual)
+    }
+
+    override fun assertNotSame(message: String?, illegal: Any?, actual: Any?) {
+        e = illegal
+        a = actual
+        super.assertNotSame(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, null)
+        } else {
+            invokeHook(true, lazyMessage)
+        }
+    }
+
+    override fun assertTrue(message: String?, actual: Boolean) {
+        assertTrue({ message }, actual)
+    }
+
+    override fun fail(message: String?): Nothing {
+        fail(message, null)
+    }
+
+    @SinceKotlin("1.4")
+    override fun fail(message: String?, cause: Throwable?): Nothing {
+        failWithMessage({ message }, cause)
+    }
+
+    private inline fun failWithMessage(lazyMessage: () -> String?, cause: Throwable?): Nothing {
+        val message = lazyMessage()
+        invokeHook(false) { message }
+        throw AssertionErrorWithCause(message, cause)
+    }
+
+    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/wasm/src/main/kotlin/kotlin/test/TestApi.kt b/libraries/kotlin.test/wasm/src/main/kotlin/kotlin/test/TestApi.kt
new file mode 100644
index 0000000..92ebcfb0
--- /dev/null
+++ b/libraries/kotlin.test/wasm/src/main/kotlin/kotlin/test/TestApi.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+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) {
+    currentAdapter.suite(name, ignored, suiteFn)
+}
+
+@JsName("test")
+internal fun test(name: String, ignored: Boolean, testFn: () -> Any?) {
+    currentAdapter.test(name, ignored, testFn)
+}
+
+internal var currentAdapter: FrameworkAdapter = BareAdapter()
+
+//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<String, () -> 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/wasm/src/main/kotlin/kotlin/test/WasmImpl.kt b/libraries/kotlin.test/wasm/src/main/kotlin/kotlin/test/WasmImpl.kt
new file mode 100644
index 0000000..f6f6fdd
--- /dev/null
+++ b/libraries/kotlin.test/wasm/src/main/kotlin/kotlin/test/WasmImpl.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package kotlin.test
+
+import kotlin.reflect.KClass
+
+/**
+ * Takes the given [block] of test code and _doesn't_ execute it.
+ *
+ * This keeps the code under test referenced, but doesn't actually test it until it is implemented.
+ */
+actual fun todo(block: () -> Unit) {
+    println("TODO at " + block)
+}
+
+/** Platform-specific construction of AssertionError with cause */
+@Suppress("NOTHING_TO_INLINE")
+internal actual inline fun AssertionErrorWithCause(message: String?, cause: Throwable?): AssertionError =
+    AssertionError(message, cause)
+
+
+@PublishedApi
+internal actual fun <T : Throwable> checkResultIsFailure(exceptionClass: KClass<T>, message: String?, blockResult: Result<Unit>): T {
+    blockResult.fold(
+        onSuccess = {
+            asserter.fail(messagePrefix(message) + "Expected an exception of $exceptionClass to be thrown, but was completed successfully.")
+        },
+        onFailure = { e ->
+            if (exceptionClass.isInstance(e)) {
+                @Suppress("UNCHECKED_CAST")
+                return e as T
+            }
+            asserter.fail(messagePrefix(message) + "Expected an exception of $exceptionClass to be thrown, but was $e", e)
+        }
+    )
+}
+
+
+/**
+ * Provides the JS implementation of asserter
+ */
+internal actual fun lookupAsserter(): Asserter = DefaultWasmAsserter
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index df59aca..baa62e7 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -500,7 +500,8 @@
             ":kotlin-test",
             ":kotlin-test:kotlin-test-js",
             ":kotlin-test:kotlin-test-js-ir",
-            ":kotlin-test:kotlin-test-js:kotlin-test-js-it"
+            ":kotlin-test:kotlin-test-js:kotlin-test-js-it",
+            ":kotlin-test:kotlin-test-wasm"
 
     project(':kotlin-stdlib-common').projectDir = "$rootDir/libraries/stdlib/common" as File
     project(':kotlin-stdlib').projectDir = "$rootDir/libraries/stdlib/jvm" as File
@@ -520,6 +521,7 @@
     project(':kotlin-test:kotlin-test-js').projectDir = "$rootDir/libraries/kotlin.test/js" as File
     project(':kotlin-test:kotlin-test-js-ir').projectDir = "$rootDir/libraries/kotlin.test/js-ir" as File
     project(':kotlin-test:kotlin-test-js:kotlin-test-js-it').projectDir = "$rootDir/libraries/kotlin.test/js/it" as File
+    project(':kotlin-test:kotlin-test-wasm').projectDir = "$rootDir/libraries/kotlin.test/wasm" as File
 }
 include ":compiler:android-tests"