diff --git a/tutorials/Web/README.md b/tutorials/Web/README.md index b0795d75c9..f9ac621ab5 100644 --- a/tutorials/Web/README.md +++ b/tutorials/Web/README.md @@ -10,3 +10,5 @@ [Handing Events](Events_Handling/README.md) - a short overview of Events handling with compose web [Style DSL](Style_Dsl/README.md) - about styling the composable components in web + +[Using test-utils](Using_Test_Utils/README.md) - how to use test-utils for DOM DSL unit testing diff --git a/tutorials/Web/Using_Test_Utils/README.md b/tutorials/Web/Using_Test_Utils/README.md new file mode 100644 index 0000000000..058b8218fc --- /dev/null +++ b/tutorials/Web/Using_Test_Utils/README.md @@ -0,0 +1,99 @@ +# Using test-utils for DOM DSL unit testing + +### Dependencies + +It's necessary to add `compose.web.testUtils` to jsTest dependencies: + +``` kotlin +sourceSets { + val jsMain by getting { + dependencies { + implementation(compose.web.core) + implementation(compose.runtime) + //.... + } + } + val jsTest by getting { + implementation(kotlin("test-js")) + implementation(compose.web.testUtils) + //... + } +} +``` + + +### Example + +``` kotlin +// This is a function that we want to test +@Composable +fun TestButton(text: String, onButtonClick: () -> Unit) { + Button(attrs = { + onClick { onButtonClick() } + }) { + Text(text) + } +} +``` + +Let's add a test to ensure that button has correct text, and it's onClick works properly. +``` kotlin +import org.jetbrains.compose.web.testutils.ComposeWebExperimentalTestsApi +import org.jetbrains.compose.web.testutils.runTest +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import org.w3c.dom.HTMLButtonElement +import kotlin.test.Test +import kotlin.test.assertEquals + +@OptIn(ComposeWebExperimentalTestsApi::class) +class TestsForButton { + + @Test + fun testButton() = runTest { + var counter by mutableStateOf(1) + + composition { + TestButton(text = "$counter") { + counter++ + } + } + + assertEquals("", root.innerHTML) + + (root.firstChild!! as HTMLButtonElement).click() + waitForRecompositionComplete() + assertEquals("", root.innerHTML) + + counter = 10 + waitForRecompositionComplete() + assertEquals("", root.innerHTML) + } +} +``` + +### Let's break it down: + +### `runTest { ... }` +Provides the TestScope with useful functions to configure the test. + +### `composition { ... }` +Takes a @Composable block with a content that we want to test. +It will automatically build and mount DOM into `root` element. + +### `root` +It's not supposed to be used for elements manipulation. +It's mostly useful to make assertions on the html content (e.g. `root.innerHtml`) + +### `nextChild() and currentChild()` +Under the hood `nextChild()` iterates over `root` children, providing convenient access to them. + +`currentChild()` doesn't move the iterator and returns the same element every time until `nextChild()` called. + +### `waitForRecompositionComplete()` +It suspends until recomposition completes. It's useful when state changes, and we want to test that content updates as well. `waitForRecompositionComplete` needs to be called after state change and before assertions. + +### `waitForChanges(id: String)` +It suspends until any change occur in the element with `id`. +It's also useful to ensure that state changes make corresponding updates to the content.