You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
83 lines
2.6 KiB
83 lines
2.6 KiB
4 years ago
|
import androidx.compose.runtime.Composable
|
||
|
import androidx.compose.runtime.Composition
|
||
|
import org.jetbrains.compose.web.css.*
|
||
|
import org.jetbrains.compose.web.css.selectors.*
|
||
|
import org.jetbrains.compose.web.attributes.*
|
||
|
import org.jetbrains.compose.web.dom.*
|
||
|
import org.jetbrains.compose.web.*
|
||
|
import kotlinx.browser.document
|
||
|
import kotlinx.browser.window
|
||
|
import kotlinx.coroutines.CoroutineScope
|
||
|
import kotlinx.coroutines.MainScope
|
||
|
import kotlinx.coroutines.promise
|
||
|
import kotlinx.dom.clear
|
||
|
import org.w3c.dom.HTMLElement
|
||
|
import org.w3c.dom.MutationObserver
|
||
|
import org.w3c.dom.MutationObserverInit
|
||
|
import kotlin.coroutines.resume
|
||
|
import kotlin.coroutines.suspendCoroutine
|
||
|
|
||
|
private val testScope = MainScope()
|
||
|
|
||
|
class TestScope : CoroutineScope by testScope {
|
||
|
|
||
|
val root = "div".asHtmlElement()
|
||
|
|
||
|
fun composition(content: @Composable () -> Unit): Composition {
|
||
|
root.clear()
|
||
|
return renderComposable(root) {
|
||
|
content()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
suspend fun waitChanges() {
|
||
|
waitForChanges(root)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal fun runTest(block: suspend TestScope.() -> Unit): dynamic {
|
||
|
val scope = TestScope()
|
||
|
return scope.promise { block(scope) }
|
||
|
}
|
||
|
|
||
|
internal fun runBlockingTest(
|
||
|
block: suspend CoroutineScope.() -> Unit
|
||
|
): dynamic = testScope.promise { this.block() }
|
||
|
|
||
|
internal fun String.asHtmlElement() = document.createElement(this) as HTMLElement
|
||
|
|
||
|
/* Currently, the recompositionRunner relies on AnimationFrame to run the recomposition and
|
||
|
applyChanges. Therefore we can use this method after updating the state and before making
|
||
|
assertions.
|
||
|
|
||
|
If tests get broken, then DefaultMonotonicFrameClock need to be checked if it still
|
||
|
uses window.requestAnimationFrame */
|
||
|
internal suspend fun waitForAnimationFrame() {
|
||
|
suspendCoroutine<Unit> { continuation ->
|
||
|
window.requestAnimationFrame {
|
||
|
continuation.resume(Unit)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private object MutationObserverOptions : MutationObserverInit {
|
||
|
override var childList: Boolean? = true
|
||
|
override var attributes: Boolean? = true
|
||
|
override var characterData: Boolean? = true
|
||
|
override var subtree: Boolean? = true
|
||
|
override var attributeOldValue: Boolean? = true
|
||
|
}
|
||
|
|
||
|
internal suspend fun waitForChanges(elementId: String) {
|
||
|
waitForChanges(document.getElementById(elementId) as HTMLElement)
|
||
|
}
|
||
|
|
||
|
internal suspend fun waitForChanges(element: HTMLElement) {
|
||
|
suspendCoroutine<Unit> { continuation ->
|
||
|
val observer = MutationObserver { mutations, observer ->
|
||
|
continuation.resume(Unit)
|
||
|
observer.disconnect()
|
||
|
}
|
||
|
observer.observe(element, MutationObserverOptions)
|
||
|
}
|
||
|
}
|