Testing
Render components with TestBed, query the DOM with KQuery, simulate events, and assert results.
The kraft-testing module provides two main tools: TestBed for rendering components into
a real DOM, and KQuery for querying and interacting with the rendered elements.
// build.gradle.kts
jsTest {
dependencies {
implementation("io.peekandpoke.kraft:testing:$kraftVersion")
}
} TestBed
Render a component (or raw HTML) into a real browser DOM and run assertions against it:
@Test
fun counter_shows_initial_value() = TestBed.preact(
view = { Counter(start = 5) },
) { root ->
root.selectCss(".value").textContent() shouldBe "5"
} The root parameter is a KQuery<Element> wrapping the rendered DOM tree. The test
block is a suspend function — you can use delay() to wait for async state changes.
@Test
fun clicking_plus_increments() = TestBed.preact(
view = { Counter(start = 0) },
) { root ->
// Click the button
root.selectCss("button.plus").click()
delay(10) // wait for re-render
// Assert the result
root.selectCss(".value").textContent() shouldBe "1"
} KQuery — selecting elements
KQuery wraps a list of DOM elements. Every operation works on all matched elements — like jQuery. An empty query is safe: methods return empty lists or no-op on mutations.
// Select by CSS selector
val buttons = root.selectCss("button")
val inputs = root.selectCss<HTMLInputElement>("input[type=text]")
// Select by debug-id attribute (set via debugId() in ultra:html)
val header = root.selectDebugId("page-header")
val emailInput = root.selectDebugId<HTMLInputElement>("email-field")
// Select within results (chaining)
val submitBtn = root.selectCss("form").selectCss("button.submit") KQuery — text & HTML
| Method | Returns | Description |
|---|---|---|
textContent(glue) | String | Combined text of all elements |
containsText(text) | Boolean | Any element contains the text |
innerHTML() | List<String> | Inner HTML of each element |
outerHTML() | List<String> | Outer HTML of each element |
KQuery — attributes & classes
| Method | Returns | Description |
|---|---|---|
attr(name) | List<String?> | Attribute value for each element |
allHaveAttr(name) | Boolean | Every element has the attribute |
anyHasAttr(name) | Boolean | Any element has the attribute |
classes() | List<Set<String>> | CSS classes for each element |
allHaveClass(cls) | Boolean | Every element has the class |
anyHasClass(cls) | Boolean | Any element has the class |
KQuery — form state
| Method | Returns | Description |
|---|---|---|
values() | List<String?> | Value of each input/textarea/select |
setValue(val) | KQuery | Set value on all elements (no events fired) |
typeText(text) | KQuery | Set value + fire input and change events |
checkedStates() | List<Boolean> | Checked state of each input |
allChecked() | Boolean | All inputs are checked |
check() / uncheck() | KQuery | Set checked + fire change event |
disabledStates() | List<Boolean> | Disabled state of each element |
allDisabled() / allEnabled() | Boolean | Check enabled/disabled state |
// Type into an input and verify the component reacts
root.selectCss("#search-input").typeText("hello")
delay(10)
root.selectCss(".results").textContent() shouldContain "hello"
// Check a checkbox
root.selectCss("#agree-checkbox").check()
delay(10)
root.selectCss("button.submit").allEnabled() shouldBe true KQuery — traversal
| Method | Returns | Description |
|---|---|---|
parents() | KQuery | Parent elements (deduplicated) |
children() | KQuery | All direct children |
first() | KQuery | First element only |
last() | KQuery | Last element only |
nth(index) | KQuery | Element at index |
filterElements { ... } | KQuery | Filter by predicate |
// Get the third list item
root.selectCss("li").nth(2).textContent() shouldBe "Third"
// Get children of a container
root.selectCss("#list").children().size shouldBe 5
// Filter active items
root.selectCss(".item").filterElements { it.classList.contains("active") } KQuery — event simulation
Every standard DOM event has a dispatch helper on KQuery. Events fire on all matched elements:
// Mouse events
root.selectCss("#btn").click()
root.selectCss("#area").dblClick()
root.selectCss("#handle").mouseDown()
root.selectCss("#target").mouseEnter()
root.selectCss("#target").mouseLeave()
root.selectCss("#menu").contextMenu()
// Keyboard events (with optional key/code)
root.selectCss("input").keyDown(key = "Enter")
root.selectCss("input").keyUp(key = "Escape")
// Focus
root.selectCss("input").focus()
root.selectCss("input").blur()
// Pointer events
root.selectCss("#canvas").pointerDown()
root.selectCss("#canvas").pointerMove()
root.selectCss("#canvas").pointerUp()
// Drag events
root.selectCss("#draggable").dragStart()
root.selectCss("#dropzone").dragEnter()
root.selectCss("#dropzone").drop()
// Touch events
root.selectCss("#slider").touchStart()
root.selectCss("#slider").touchMove()
root.selectCss("#slider").touchEnd()
// Clipboard
root.selectCss("#editor").copy()
root.selectCss("#editor").paste()
// Animation / transition
root.selectCss(".animated").animationEnd()
root.selectCss(".fading").transitionEnd()
// Scroll / load
root.selectCss(".scrollable").scroll()
// Low-level (any event type)
root.selectCss("#target").dispatch("customevent", bubbles = true) Waiting for async content
Use awaitCss to poll for elements that appear after async operations:
// Wait up to 2 seconds for a loading spinner to disappear and content to appear
val items = root.awaitCss(".item-list", timeoutMs = 2000)
items.size shouldBeGreaterThan 0 Full example
class TodoAppSpec : StringSpec({
"adding a todo shows it in the list" {
TestBed.preact({ TodoApp() }) { root ->
// Type a new todo
root.selectCss("input.new-todo").typeText("Write tests")
delay(10)
// Submit the form
root.selectCss("form").submit()
delay(50)
// Verify it appears in the list
val items = root.selectCss(".todo-item")
items.size shouldBe 1
items.textContent() shouldContain "Write tests"
}
}
"checking a todo marks it as done" {
TestBed.preact({ TodoApp() }) { root ->
// Add a todo first
root.selectCss("input.new-todo").typeText("Test this")
root.selectCss("form").submit()
delay(50)
// Check the checkbox
root.selectCss(".todo-item input[type=checkbox]").check()
delay(10)
// Verify the done class is applied
root.selectCss(".todo-item").allHaveClass("done") shouldBe true
}
}
})