Browse Source
Co-authored-by: Oleksandr Karpovich <oleksandr.karpovich@jetbrains.com>pull/1499/head
Oleksandr Karpovich
3 years ago
committed by
GitHub
2 changed files with 101 additions and 0 deletions
@ -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("<button>1</button>", root.innerHTML) |
||||||
|
|
||||||
|
(root.firstChild!! as HTMLButtonElement).click() |
||||||
|
waitForRecompositionComplete() |
||||||
|
assertEquals("<button>2</button>", root.innerHTML) |
||||||
|
|
||||||
|
counter = 10 |
||||||
|
waitForRecompositionComplete() |
||||||
|
assertEquals("<button>10</button>", 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. |
Loading…
Reference in new issue