Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support wasmJs target #771

Merged
merged 10 commits into from
Jan 11, 2024
3 changes: 2 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ kotlin.code.style=official
org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=true

# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with the app's APK
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true

kotlin.mpp.androidSourceSetLayoutVersion=2
kotlin.mpp.applyDefaultHierarchyTemplate=false

# For compatibility with Kotlin 1.9.0, see https://github.com/badoo/Reaktive/issues/697
Expand Down
11 changes: 4 additions & 7 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
[versions]
kotlin = "1.9.21"
kotlinx-coroutines = "1.7.3"
kotlinx-coroutines = "1.8.0-RC2"
detekt = "1.22.0"

[libraries]
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
kotlin-stdlib-js = { module = "org.jetbrains.kotlin:kotlin-stdlib-js", version.ref = "kotlin" }
kotlin-stdlib-common = { module = "org.jetbrains.kotlin:kotlin-stdlib-common", version.ref = "kotlin" }
kotlin-stdlib-wasm-js = { module = "org.jetbrains.kotlin:kotlin-stdlib-wasm-js", version.ref = "kotlin" }
IlyaGulya marked this conversation as resolved.
Show resolved Hide resolved

kotlin-test-common = { module = "org.jetbrains.kotlin:kotlin-test-common", version.ref = "kotlin" }
kotlin-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kotlin" }
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
kotlin-test-annotations = { module = "org.jetbrains.kotlin:kotlin-test-annotations-common", version.ref = "kotlin" }
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
kotlin-test-annotations = { module = "org.jetbrains.kotlin:kotlin-test-annotations", version.ref = "kotlin" }

kotlinx-compatibility = "org.jetbrains.kotlinx:binary-compatibility-validator:0.13.2"
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class JsPlugin : Plugin<Project> {

private fun configureJsCompilation(target: Project) {
target.extensions.configure(KotlinMultiplatformExtension::class.java) {
js(IR) {
js {
browser {
testTask {
useKarma {
Expand All @@ -31,12 +31,12 @@ class JsPlugin : Plugin<Project> {
}
sourceSets.getByName("jsMain") {
dependencies {
implementation(project.getLibrary("kotlin-stdlib-js"))
implementation(project.getLibrary("kotlin-stdlib"))
}
}
sourceSets.getByName("jsTest") {
dependencies {
implementation(project.getLibrary("kotlin-test-js"))
implementation(project.getLibrary("kotlin-test"))
}
}
}
Expand Down
IlyaGulya marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,9 @@ class MppConfigurationPlugin : Plugin<Project> {
target.version = target.findProperty("reaktive_version") as Any
target.extensions.configure(KotlinMultiplatformExtension::class.java) {
sourceSets {
maybeCreate("commonMain").dependencies { implementation(target.getLibrary("kotlin-stdlib-common")) }
maybeCreate("commonMain").dependencies { implementation(target.getLibrary("kotlin-stdlib")) }
maybeCreate("commonTest").dependencies {
implementation(target.getLibrary("kotlin-test-common"))
implementation(target.getLibrary("kotlin-test-annotations"))
implementation(target.getLibrary("kotlin-test"))
}
}

Expand All @@ -48,6 +47,7 @@ class MppConfigurationPlugin : Plugin<Project> {
setupAndroidTarget(project)
setupJvmTarget(project)
setupJsTarget(project)
setupWasmJsTarget(project)
setupLinuxX64Target(project)
setupIosTargets(project)

Expand All @@ -62,6 +62,9 @@ class MppConfigurationPlugin : Plugin<Project> {
maybeCreate("jvmJsCommonMain").dependsOn(getByName("commonMain"))
maybeCreate("jvmJsCommonTest").dependsOn(getByName("commonTest"))

maybeCreate("jsWasmJsCommonMain").dependsOn(getByName("commonMain"))
IlyaGulya marked this conversation as resolved.
Show resolved Hide resolved
maybeCreate("jsWasmJsCommonTest").dependsOn(getByName("commonTest"))

maybeCreate("jvmNativeCommonMain").dependsOn(getByName("commonMain"))
maybeCreate("jvmNativeCommonTest").dependsOn(getByName("commonTest"))

Expand All @@ -80,8 +83,23 @@ class MppConfigurationPlugin : Plugin<Project> {
maybeCreate("androidMain").dependsOn(getByName("jvmCommonMain"))
maybeCreate("androidUnitTest").dependsOn(getByName("jvmCommonTest"))

maybeCreate("jsMain").dependsOn(getByName("jvmJsCommonMain"))
maybeCreate("jsTest").dependsOn(getByName("jvmJsCommonTest"))
maybeCreate("jsMain").apply {
IlyaGulya marked this conversation as resolved.
Show resolved Hide resolved
dependsOn(getByName("jvmJsCommonMain"))
dependsOn(getByName("jsWasmJsCommonMain"))
}
maybeCreate("jsTest").apply {
dependsOn(getByName("jvmJsCommonTest"))
dependsOn(getByName("jsWasmJsCommonTest"))
}

maybeCreate("wasmJsMain").apply {
IlyaGulya marked this conversation as resolved.
Show resolved Hide resolved
dependsOn(getByName("jvmJsCommonMain"))
IlyaGulya marked this conversation as resolved.
Show resolved Hide resolved
dependsOn(getByName("jsWasmJsCommonMain"))
}
maybeCreate("wasmJsTest").apply {
dependsOn(getByName("jvmJsCommonTest"))
dependsOn(getByName("jsWasmJsCommonTest"))
}

maybeCreate("nativeCommonMain").dependsOn(getByName("jvmNativeCommonMain"))
maybeCreate("nativeCommonTest").dependsOn(getByName("jvmNativeCommonTest"))
Expand Down Expand Up @@ -162,7 +180,7 @@ class MppConfigurationPlugin : Plugin<Project> {
}
sourceSets {
maybeCreate("androidMain").dependencies { implementation(project.getLibrary("kotlin-stdlib")) }
maybeCreate("androidUnitTest").dependencies { implementation(project.getLibrary("kotlin-test-junit")) }
maybeCreate("androidUnitTest").dependencies { implementation(project.getLibrary("kotlin-test")) }
}
}
}
Expand All @@ -179,7 +197,7 @@ class MppConfigurationPlugin : Plugin<Project> {
}
sourceSets {
maybeCreate("jvmMain").dependencies { implementation(project.getLibrary("kotlin-stdlib")) }
maybeCreate("jvmTest").dependencies { implementation(project.getLibrary("kotlin-test-junit")) }
maybeCreate("jvmTest").dependencies { implementation(project.getLibrary("kotlin-test")) }
}
}
}
Expand All @@ -188,6 +206,10 @@ class MppConfigurationPlugin : Plugin<Project> {
project.apply<JsPlugin>()
}

private fun setupWasmJsTarget(project: Project) {
project.apply<WasmJsPlugin>()
}

private fun setupLinuxX64Target(project: Project) {
project.kotlin {
linuxX64 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ enum class Target(
ALL_LINUX_HOSTED(linuxHosted = true, macOsHosted = false),
JVM(linuxHosted = true, macOsHosted = false),
JS(linuxHosted = true, macOsHosted = false),
WASM_JS(linuxHosted = true, macOsHosted = false),
IlyaGulya marked this conversation as resolved.
Show resolved Hide resolved
LINUX(linuxHosted = true, macOsHosted = false),
ALL_MACOS_HOSTED(linuxHosted = false, macOsHosted = true),
IOS(linuxHosted = false, macOsHosted = true),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.badoo.reaktive.configuration

import com.badoo.reaktive.getLibrary
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl

@OptIn(ExperimentalWasmDsl::class)
class WasmJsPlugin : Plugin<Project> {

override fun apply(target: Project) {
configureWasmJsCompilation(target)
}

private fun configureWasmJsCompilation(target: Project) {
target.extensions.configure(KotlinMultiplatformExtension::class.java) {
wasmJs {
browser()
disableIfUndefined(Target.WASM_JS)
}
sourceSets.getByName("wasmJsMain") {
dependencies {
implementation(project.getLibrary("kotlin-stdlib"))
}
}
sourceSets.getByName("wasmJsTest") {
dependencies {
implementation(project.getLibrary("kotlin-test"))
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ class PublishConfigurationPlugin : Plugin<Project> {
KotlinMultiplatformPlugin.METADATA_TARGET_NAME to Target.shouldPublishTarget(project, Target.META),
"jvm" to Target.shouldPublishTarget(project, Target.JVM),
"js" to Target.shouldPublishTarget(project, Target.JS),
"wasmJs" to Target.shouldPublishTarget(project, Target.WASM_JS),
"androidDebug" to Target.shouldPublishTarget(project, Target.JVM),
"androidRelease" to Target.shouldPublishTarget(project, Target.JVM),
"linuxX64" to Target.shouldPublishTarget(project, Target.LINUX),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.badoo.reaktive.test.single

expect class AsyncTestResult
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@ import com.badoo.reaktive.single.Single
* @param assertSuccess when provided, it will be called in case of [Single] success.
* This gives an opportunity to assert the result.
*/
expect fun <T> Single<T>.testAwait(assertError: ((Throwable) -> Unit)? = null, assertSuccess: (T) -> Unit)
expect fun <T> Single<T>.testAwait(assertError: ((Throwable) -> Unit)? = null, assertSuccess: (T) -> Unit): AsyncTestResult
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class TestAwaitTest {
.testAwait(assertSuccess = { assertEquals(1, it) })

@Test
fun succeeds_WHEN_upstream_failed_and_assertError_did_not_throw() {
fun succeeds_WHEN_upstream_failed_and_assertError_did_not_throw(): AsyncTestResult {
val error = Exception()

return singleOfError<Nothing>(error)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.badoo.reaktive.test.single

@Suppress("UnusedPrivateMember")
@JsName("Promise")
actual external class AsyncTestResult(
executor: (resolve: (Unit) -> Unit, reject: (Throwable) -> Unit) -> Unit,
)
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package com.badoo.reaktive.test.single

import com.badoo.reaktive.single.Single
import com.badoo.reaktive.single.asPromise
import com.badoo.reaktive.single.doOnBeforeSuccess
import com.badoo.reaktive.single.map
import com.badoo.reaktive.single.onErrorReturn
import com.badoo.reaktive.single.subscribe

actual fun <T> Single<T>.testAwait(assertError: ((Throwable) -> Unit)?, assertSuccess: (T) -> Unit): dynamic =
actual fun <T> Single<T>.testAwait(assertError: ((Throwable) -> Unit)?, assertSuccess: (T) -> Unit): AsyncTestResult =
if (assertError == null) {
doOnBeforeSuccess(assertSuccess)
.asPromise()
.asTestResult()
} else {
map { TestAwaitResult.Success(it) }
.onErrorReturn { TestAwaitResult.Error(it) }
Expand All @@ -19,7 +19,15 @@ actual fun <T> Single<T>.testAwait(assertError: ((Throwable) -> Unit)?, assertSu
is TestAwaitResult.Error -> assertError(result.error)
}
}
.asPromise()
.asTestResult()
}

private fun <T> Single<T>.asTestResult(): AsyncTestResult =
AsyncTestResult { resolve, reject ->
subscribe(
onSuccess = { resolve(Unit) },
onError = reject,
)
}

private sealed class TestAwaitResult<out T> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.badoo.reaktive.test.single

@Suppress("ACTUAL_WITHOUT_EXPECT") // We never instantiate TestResult, see https://youtrack.jetbrains.com/issue/KT-64199
actual typealias AsyncTestResult = Unit
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ actual fun <T> Single<T>.testAwait(assertError: ((Throwable) -> Unit)?, assertSu

assertSuccess(result)
}

return
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.badoo.reaktive.test.single

@Suppress("UnusedPrivateMember")
@JsName("Promise")
actual external class AsyncTestResult(
executor: (resolve: (JsAny) -> Unit, reject: (JsAny) -> Unit) -> Unit,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.badoo.reaktive.test.single

import com.badoo.reaktive.single.Single
import com.badoo.reaktive.single.doOnBeforeSuccess
import com.badoo.reaktive.single.map
import com.badoo.reaktive.single.onErrorReturn
import com.badoo.reaktive.single.subscribe

actual fun <T> Single<T>.testAwait(assertError: ((Throwable) -> Unit)?, assertSuccess: (T) -> Unit): AsyncTestResult =
if (assertError == null) {
doOnBeforeSuccess(assertSuccess)
.asTestResult()
} else {
map { TestAwaitResult.Success(it) }
.onErrorReturn { TestAwaitResult.Error(it) }
.doOnBeforeSuccess { result ->
when (result) {
is TestAwaitResult.Success -> assertSuccess(result.value)
is TestAwaitResult.Error -> assertError(result.error)
}
}
.asTestResult()
}

private fun <T> Single<T>.asTestResult(): AsyncTestResult =
AsyncTestResult { resolve, reject ->
subscribe(
onSuccess = { resolve(Unit.toJsReference()) },
onError = { reject(it.toJsReference()) },
)
}

private sealed class TestAwaitResult<out T> {
class Success<out T>(val value: T) : TestAwaitResult<T>()
class Error(val error: Throwable) : TestAwaitResult<Nothing>()
}
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ class RepeatWhenTest : ObservableToObservableTests by ObservableToObservableTest
.test()

upstream.onComplete()
assertSame(timeRef, 1)
assertEquals(timeRef, 1)
upstream.onComplete()
assertSame(timeRef, 2)
assertEquals(timeRef, 2)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ class RepeatWhenTest : SingleToObservableTests by SingleToObservableTestsImpl({
.test()

upstream.onComplete()
assertSame(attemptVar, 1)
assertEquals(attemptVar, 1)
upstream.onComplete()
assertSame(attemptVar, 2)
assertEquals(attemptVar, 2)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ private class SubjectGenericTestsImpl(
subject.onNext(0)
subject.onComplete()

observers.forEach(TestObservableObserver<*>::assertComplete)
observers.forEach { it.assertComplete() }
}

override fun delivers_error_to_all_observers_WHEN_error_produced() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.badoo.reaktive.scheduler

import com.badoo.reaktive.global.external.globalThis

internal actual fun jsSetTimeout(task: () -> Unit, delayMillis: Int): Int =
globalThis.setTimeout(task, delayMillis)

internal actual fun jsSetInterval(task: () -> Unit, delayMillis: Int): Int =
globalThis.setInterval(task, delayMillis)

internal actual fun jsClearTimeout(id: Int) {
globalThis.clearTimeout(id)
}

internal actual fun jsClearInterval(id: Int) {
globalThis.clearInterval(id)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.badoo.reaktive.disposable

import kotlin.js.JsName

@JsName("disposableWithCallback")
@Suppress("FunctionName")
actual inline fun Disposable(crossinline onDispose: () -> Unit): Disposable =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.badoo.reaktive.scheduler

internal expect fun jsSetTimeout(task: () -> Unit, delayMillis: Int): Int

internal expect fun jsSetInterval(task: () -> Unit, delayMillis: Int): Int

internal expect fun jsClearTimeout(id: Int)

internal expect fun jsClearInterval(id: Int)
Loading