Improve tests

This commit is contained in:
2026-01-20 21:26:55 +01:00
parent 841960ecc5
commit b325d2b653
6 changed files with 158 additions and 23 deletions

View File

@@ -120,7 +120,6 @@ fun App(
textAlign = TextAlign.End textAlign = TextAlign.End
) )
}, },
spacing = spacing,
game, resetCluesBeacon game, resetCluesBeacon
) )
EndOfGame(isSolved = isSolved, time = time, onRestart = onNewGame) EndOfGame(isSolved = isSolved, time = time, onRestart = onNewGame)

View File

@@ -8,7 +8,6 @@ import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Placeable import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Constraints.Companion.fixed import androidx.compose.ui.unit.Constraints.Companion.fixed
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import ch.dissem.yaep.ui.common.focus.CluesSelectionManager import ch.dissem.yaep.ui.common.focus.CluesSelectionManager
import ch.dissem.yaep.ui.common.focus.FocusFollowingFocusable import ch.dissem.yaep.ui.common.focus.FocusFollowingFocusable
@@ -29,7 +28,6 @@ fun AdaptiveGameLayout(
horizontalClues: @Composable (SelectionManager<*>) -> Unit, horizontalClues: @Composable (SelectionManager<*>) -> Unit,
verticalClues: @Composable (SelectionManager<*>) -> Unit, verticalClues: @Composable (SelectionManager<*>) -> Unit,
time: @Composable () -> Unit, time: @Composable () -> Unit,
spacing: Dp = 8.dp,
vararg resetBeacons: Any vararg resetBeacons: Any
) { ) {
val gridFocusable = remember(*resetBeacons) { selectionManager.add() } val gridFocusable = remember(*resetBeacons) { selectionManager.add() }
@@ -80,7 +78,7 @@ fun AdaptiveGameLayout(
val timeMeasurable = measurables[6][0] val timeMeasurable = measurables[6][0]
val dividerMeasurables = measurables[7] val dividerMeasurables = measurables[7]
val spacingPx = spacing.roundToPx() val spacingPx = 8.dp.roundToPx()
when (aspectRatio) { when (aspectRatio) {
PORTRAIT -> { PORTRAIT -> {

View File

@@ -1,20 +1,30 @@
package ch.dissem.yaep.ui.common package ch.dissem.yaep.ui.common
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Size import androidx.compose.ui.geometry.Size
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.SkikoComposeUiTest import androidx.compose.ui.test.SkikoComposeUiTest
import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performKeyInput
import androidx.compose.ui.test.pressKey
import androidx.compose.ui.test.runSkikoComposeUiTest import androidx.compose.ui.test.runSkikoComposeUiTest
import androidx.compose.ui.test.withKeyDown
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import ch.dissem.yaep.domain.Game import ch.dissem.yaep.domain.Game
import ch.dissem.yaep.domain.UnsolvablePuzzleException import ch.dissem.yaep.domain.UnsolvablePuzzleException
import ch.dissem.yaep.ui.common.focus.FocusFollowingSelectionManager import ch.dissem.yaep.ui.common.focus.FocusFollowingSelectionManager
import ch.dissem.yaep.ui.common.focus.GridSelectionManager
import ch.tutteli.atrium.api.fluent.en_GB.notToEqualNull
import ch.tutteli.atrium.api.fluent.en_GB.toBeAnInstanceOf
import ch.tutteli.atrium.api.fluent.en_GB.toEqual import ch.tutteli.atrium.api.fluent.en_GB.toEqual
import ch.tutteli.atrium.api.fluent.en_GB.toHaveElementsAndAll
import ch.tutteli.atrium.api.fluent.en_GB.toHaveSize
import ch.tutteli.atrium.api.verbs.expect import ch.tutteli.atrium.api.verbs.expect
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.fail import kotlin.test.fail
@@ -25,7 +35,7 @@ class AppTest {
var resetCluesBeacon by mutableStateOf(Any()) var resetCluesBeacon by mutableStateOf(Any())
@Test @Test
fun ensure_app_can_be_rendered_on_1920x1080_and_can_be_solved() = ensure_app_can_be_rendered_on( fun ensure_app_can_be_rendered_on_1920x1080_and_can_be_solved() = showApp(
Size(1920f, 1080f) Size(1920f, 1080f)
) { ) {
val clues = game.clues val clues = game.clues
@@ -49,31 +59,89 @@ class AppTest {
} while (removedOptions) } while (removedOptions)
expect(game.isSolved).toEqual(true) expect(game.isSolved).toEqual(true)
onNodeWithTag("EndOfGame.solved_time").assertExists("End of Game must be shown when puzzle is solved") onNodeWithTag("EndOfGame.solved_time")
.assertExists("End of Game must be shown when puzzle is solved")
} }
@Test @Test
fun ensure_app_can_be_rendered_on_1080x1920() = ensure_app_can_be_rendered_on( fun ensure_app_can_be_rendered_on_1080x1920_and_keyboard_selection_and_deselection_works() = showApp(
Size(1080f, 1920f) Size(1080f, 1920f)
) ) {
expect(game.grid[0][1].selection).toEqual(null)
expect(FocusFollowingSelectionManager.focused).notToEqualNull()
expect(FocusFollowingSelectionManager.focused?.child).toBeAnInstanceOf<GridSelectionManager>()
onRoot().performKeyInput {
pressKey(Key.DirectionRight)
pressKey(Key.One)
}
expect(game.grid[0][1].selection).notToEqualNull()
onRoot().performKeyInput {
pressKey(Key.Backspace)
}
expect(game.grid[0][1].selection).toEqual(null)
expect(game.grid.map { it.options }).toHaveElementsAndAll {
toHaveSize(6)
}
}
@Test @Test
fun ensure_app_can_be_rendered_on_800x600() = ensure_app_can_be_rendered_on( fun ensure_app_can_be_rendered_on_800x600_and_keyboard_option_removal_works() = showApp(
Size(800f, 600f) Size(800f, 600f)
) ) {
val gridCell = game.grid[0][1]
expect(gridCell.selection).toEqual(null)
onRoot().performKeyInput {
pressKey(Key.DirectionRight)
withKeyDown(Key.ShiftLeft) {
expect(gridCell.options).toHaveSize(6)
pressKey(Key.One)
expect(gridCell.options).toHaveSize(5)
pressKey(Key.Two)
expect(gridCell.options).toHaveSize(4)
pressKey(Key.Three)
expect(gridCell.options).toHaveSize(3)
pressKey(Key.Four)
expect(gridCell.options).toHaveSize(2)
pressKey(Key.Five)
expect(gridCell.options).toHaveSize(1)
}
}
expect(gridCell.selection).notToEqualNull()
}
@Test @Test
fun ensure_app_can_be_rendered_on_600x800() = ensure_app_can_be_rendered_on( fun ensure_app_can_be_rendered_on_600x800_and_numpad_option_selection_works() = showApp(
Size(600f, 800f) Size(600f, 800f)
) ) {
val gridCell = game.grid[0][1]
fun ensure_app_can_be_rendered_on( onRoot().performKeyInput {
pressKey(Key.DirectionRight)
withKeyDown(Key.ShiftLeft) {
expect(gridCell.options).toHaveSize(6)
pressKey(Key.NumPad1)
expect(gridCell.options).toHaveSize(5)
pressKey(Key.NumPad5)
expect(gridCell.options).toHaveSize(4)
pressKey(Key.NumPad5)
expect(gridCell.options).toHaveSize(5)
pressKey(Key.NumPad4)
expect(gridCell.options).toHaveSize(4)
}
}
}
fun showApp(
screenSize: Size, screenSize: Size,
block: suspend SkikoComposeUiTest.() -> Unit = {} block: suspend SkikoComposeUiTest.() -> Unit = {}
) = runSkikoComposeUiTest(size = screenSize) { ) = runSkikoComposeUiTest(size = screenSize) {
setContent { setContent {
App( App(
modifier = Modifier.fillMaxSize(), modifier = Modifier.onKeyEvent { event ->
FocusFollowingSelectionManager.onKeyEvent(event)
},
rootSelectionManager = FocusFollowingSelectionManager, rootSelectionManager = FocusFollowingSelectionManager,
spacing = 8.dp, spacing = 8.dp,
game = game, game = game,

View File

@@ -0,0 +1,66 @@
package ch.dissem.yaep.ui.common
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.runComposeUiTest
import ch.dissem.yaep.domain.Animal
import ch.dissem.yaep.domain.Drink
import ch.dissem.yaep.domain.Fruit
import ch.dissem.yaep.domain.ItemClass
import ch.dissem.yaep.domain.Nationality
import ch.dissem.yaep.domain.Profession
import ch.dissem.yaep.domain.Transportation
import ch.tutteli.atrium.api.fluent.en_GB.notToBeBlank
import ch.tutteli.atrium.api.fluent.en_GB.toHaveElementsAndAll
import ch.tutteli.atrium.api.verbs.expect
import org.jetbrains.compose.resources.stringResource
import kotlin.test.Test
@OptIn(ExperimentalTestApi::class)
class NamesTest {
@Test
fun ensure_names_are_defined_for_animal() {
ensure_names_are_defined_for(Animal.items)
}
@Test
fun ensure_names_are_defined_for_nationality() {
ensure_names_are_defined_for(Nationality.items)
}
@Test
fun ensure_names_are_defined_for_drink() {
ensure_names_are_defined_for(Drink.items)
}
@Test
fun ensure_names_are_defined_for_profession() {
ensure_names_are_defined_for(Profession.items)
}
@Test
fun ensure_names_are_defined_for_fruit() {
ensure_names_are_defined_for(Fruit.items)
}
@Test
fun ensure_names_are_defined_for_dessert() {
ensure_names_are_defined_for(Fruit.items)
}
@Test
fun ensure_names_are_defined_for_transportation() {
ensure_names_are_defined_for(Transportation.items)
}
fun ensure_names_are_defined_for(items: List<ItemClass<*>>) {
runComposeUiTest {
var strings = listOf<String>()
setContent {
strings = items.map { stringResource(it.localName) }
}
expect(strings).toHaveElementsAndAll {
notToBeBlank()
}
}
}
}

View File

@@ -1,8 +1,6 @@
package ch.dissem.yaep.ui.common.focus package ch.dissem.yaep.ui.common.focus
import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.KeyEventType
import ch.tutteli.atrium.api.fluent.en_GB.toEqual import ch.tutteli.atrium.api.fluent.en_GB.toEqual
import ch.tutteli.atrium.api.verbs.expect import ch.tutteli.atrium.api.verbs.expect
@@ -58,10 +56,4 @@ abstract class SelectionManagerTest<F : Focusable<F>, M : SelectionManager<F>> {
} }
} }
@OptIn(InternalComposeUiApi::class)
fun keyEvent(key: Key, type: KeyEventType = KeyEventType.KeyUp) = KeyEvent(
key = key,
type = type
)
} }

View File

@@ -0,0 +1,12 @@
package ch.dissem.yaep.ui.common.focus
import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyEventType
@OptIn(InternalComposeUiApi::class)
fun keyEvent(key: Key, type: KeyEventType = KeyEventType.KeyUp) = KeyEvent(
key = key,
type = type
)