---
title: Previews and tests
description: Pretend to be any foldable with ProvideFoldState and the FoldState factories.
---

`rememberFoldState()` returns the state given to the nearest `ProvideFoldState`, and the platform's state when there is none. That makes every fold-aware composable previewable and testable on a machine with no hinge.

## The factories

| Factory | Describes |
| --- | --- |
| `FoldState.None` | A phone, a desktop window or a browser: no hinge, no fold. |
| `FoldState.closed()` | A foldable that is shut, on its outer display. |
| `FoldState.flat(windowSize, orientation, foldWidth)` | A foldable lying open, with a fold across the window. |
| `FoldState.book(windowSize, angle, foldWidth)` | Half open, vertical fold in the middle. |
| `FoldState.tabletop(windowSize, angle, foldWidth)` | Half open, horizontal fold in the middle. |

`windowSize` is in pixels. `angle` is in degrees and defaults to 90. `foldWidth` is in pixels, and 0 means a seamless panel.

For anything else, build a `FoldState` yourself from a `Hinge`, a list of `Fold`s and the cutouts.

## Previews

```kotlin
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.tooling.preview.Preview
import com.jantursky.crease.FoldAwareTwoPane
import com.jantursky.crease.FoldState
import com.jantursky.crease.ProvideFoldState

@Composable
private fun ReaderPreviewContent() {
    FoldAwareTwoPane(
        first = { Text("Ingredients") },
        second = { Text("Steps") },
    )
}

@Preview(widthDp = 840, heightDp = 640)
@Composable
private fun BookPreview() {
    ProvideFoldState(FoldState.book(Size(2520f, 1920f), angle = 100f, foldWidth = 36f)) {
        ReaderPreviewContent()
    }
}

@Preview(widthDp = 360, heightDp = 640)
@Composable
private fun PhonePreview() {
    ProvideFoldState(FoldState.None) {
        ReaderPreviewContent()
    }
}
```

`@Preview` comes from the Compose tooling preview artifact, which a Compose Multiplatform project usually has in `commonMain` already.

## Tests

With Compose's UI test API, give the screen a state and assert on what shows:

```kotlin
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.runComposeUiTest
import com.jantursky.crease.FoldAwareTwoPane
import com.jantursky.crease.FoldState
import com.jantursky.crease.ProvideFoldState
import kotlin.test.Test

@Composable
private fun ReaderScreen() {
    FoldAwareTwoPane(
        first = { Text("Ingredients") },
        second = { Text("Steps") },
    )
}

class ReaderScreenTest {
    @OptIn(ExperimentalTestApi::class)
    @Test
    fun showsBothPanesWhenHeldLikeABook() = runComposeUiTest {
        setContent {
            ProvideFoldState(FoldState.book(Size(1840f, 2208f))) {
                ReaderScreen()
            }
        }
        onNodeWithText("Ingredients").assertExists()
        onNodeWithText("Steps").assertExists()
    }

    @OptIn(ExperimentalTestApi::class)
    @Test
    fun showsOnlyTheFirstPaneOnAPhone() = runComposeUiTest {
        setContent {
            ProvideFoldState(FoldState.None) {
                ReaderScreen()
            }
        }
        onNodeWithText("Ingredients").assertExists()
        onNodeWithText("Steps").assertDoesNotExist()
    }
}
```

## Changing the fold during a test

The optional `crease-test` artifact sets the content and gives you a controller, so a test can fold and unfold the device while the composition stays alive. State the content remembers survives each change.

```kotlin
implementation("io.github.jantursky:crease-test:1.2.0")
```

```kotlin
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.runComposeUiTest
import com.jantursky.crease.FoldAwareTwoPane
import com.jantursky.crease.Posture
import com.jantursky.crease.test.FoldStatePresets
import com.jantursky.crease.test.setContentWithFold
import kotlin.test.Test

@Composable
private fun TwoPaneReader() {
    FoldAwareTwoPane(
        first = { BasicText("Ingredients") },
        second = { BasicText("Steps") },
    )
}

class FoldingReaderTest {
    @OptIn(ExperimentalTestApi::class)
    @Test
    fun secondPaneAppearsWhenTheDeviceOpensLikeABook() = runComposeUiTest {
        val fold = setContentWithFold(FoldStatePresets.Divided.flat()) { TwoPaneReader() }
        fold.moveTo(Posture.Book, FoldStatePresets.Divided.windowSize, foldWidth = 40f)
        onNodeWithText("Steps").assertExists()
    }

    @OptIn(ExperimentalTestApi::class)
    @Test
    fun playsAWholeSequence() = runComposeUiTest {
        val fold = setContentWithFold { TwoPaneReader() }
        fold.play(
            listOf(
                FoldStatePresets.Seamless.book(),
                FoldStatePresets.Seamless.tabletop(),
                FoldStatePresets.Seamless.closed(),
            ),
        ) { onNodeWithText("Ingredients").assertExists() }
    }
}
```

`setContentWithFold` returns a `FoldController`:

| Member | Does |
| --- | --- |
| `update(state)` | Shows the content the given `FoldState`, then waits until it has recomposed. |
| `moveTo(posture, windowSize, angle, foldWidth, orientation)` | Builds the state for a posture with the `FoldState` factories and applies it. |
| `play(states, afterEach)` | Applies the states in order and calls `afterEach` once the content has settled on each. |
| `state` | The state the content currently sees. |

`FoldStatePresets.Seamless` (a panel that is one piece across the fold) and `FoldStatePresets.Divided` (a 40 px division that occludes content) give a window size and fold width, and build a state for each posture. They are fixtures, not measurements of a particular device. The sizes are in pixels, so run tests at density 1.
