From b325d2b6532dda31388703d89385966882219622 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Tue, 20 Jan 2026 21:26:55 +0100 Subject: [PATCH] Improve tests --- .../kotlin/ch/dissem/yaep/ui/common/App.kt | 1 - .../ui/common/layout/adaptive game layout.kt | 4 +- .../ch/dissem/yaep/ui/common/AppTest.kt | 90 ++++++++++++++++--- .../ch/dissem/yaep/ui/common/NamesTest.kt | 66 ++++++++++++++ .../ui/common/focus/SelectionManagerTest.kt | 8 -- .../yaep/ui/common/focus/keybord utils.kt | 12 +++ 6 files changed, 158 insertions(+), 23 deletions(-) create mode 100644 commonUI/src/commonTest/kotlin/ch/dissem/yaep/ui/common/NamesTest.kt create mode 100644 commonUI/src/commonTest/kotlin/ch/dissem/yaep/ui/common/focus/keybord utils.kt diff --git a/commonUI/src/commonMain/kotlin/ch/dissem/yaep/ui/common/App.kt b/commonUI/src/commonMain/kotlin/ch/dissem/yaep/ui/common/App.kt index 5594fc5..4116a6f 100644 --- a/commonUI/src/commonMain/kotlin/ch/dissem/yaep/ui/common/App.kt +++ b/commonUI/src/commonMain/kotlin/ch/dissem/yaep/ui/common/App.kt @@ -120,7 +120,6 @@ fun App( textAlign = TextAlign.End ) }, - spacing = spacing, game, resetCluesBeacon ) EndOfGame(isSolved = isSolved, time = time, onRestart = onNewGame) diff --git a/commonUI/src/commonMain/kotlin/ch/dissem/yaep/ui/common/layout/adaptive game layout.kt b/commonUI/src/commonMain/kotlin/ch/dissem/yaep/ui/common/layout/adaptive game layout.kt index 0f63698..777ae07 100644 --- a/commonUI/src/commonMain/kotlin/ch/dissem/yaep/ui/common/layout/adaptive game layout.kt +++ b/commonUI/src/commonMain/kotlin/ch/dissem/yaep/ui/common/layout/adaptive game layout.kt @@ -8,7 +8,6 @@ import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Placeable import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Constraints.Companion.fixed -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.FocusFollowingFocusable @@ -29,7 +28,6 @@ fun AdaptiveGameLayout( horizontalClues: @Composable (SelectionManager<*>) -> Unit, verticalClues: @Composable (SelectionManager<*>) -> Unit, time: @Composable () -> Unit, - spacing: Dp = 8.dp, vararg resetBeacons: Any ) { val gridFocusable = remember(*resetBeacons) { selectionManager.add() } @@ -80,7 +78,7 @@ fun AdaptiveGameLayout( val timeMeasurable = measurables[6][0] val dividerMeasurables = measurables[7] - val spacingPx = spacing.roundToPx() + val spacingPx = 8.dp.roundToPx() when (aspectRatio) { PORTRAIT -> { diff --git a/commonUI/src/commonTest/kotlin/ch/dissem/yaep/ui/common/AppTest.kt b/commonUI/src/commonTest/kotlin/ch/dissem/yaep/ui/common/AppTest.kt index ff9394e..19d9bbc 100644 --- a/commonUI/src/commonTest/kotlin/ch/dissem/yaep/ui/common/AppTest.kt +++ b/commonUI/src/commonTest/kotlin/ch/dissem/yaep/ui/common/AppTest.kt @@ -1,20 +1,30 @@ package ch.dissem.yaep.ui.common -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier 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.SkikoComposeUiTest 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.withKeyDown import androidx.compose.ui.unit.dp import ch.dissem.yaep.domain.Game import ch.dissem.yaep.domain.UnsolvablePuzzleException 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.toHaveElementsAndAll +import ch.tutteli.atrium.api.fluent.en_GB.toHaveSize import ch.tutteli.atrium.api.verbs.expect import kotlin.test.Test import kotlin.test.fail @@ -25,7 +35,7 @@ class AppTest { var resetCluesBeacon by mutableStateOf(Any()) @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) ) { val clues = game.clues @@ -49,31 +59,89 @@ class AppTest { } while (removedOptions) 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 - 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) - ) + ) { + expect(game.grid[0][1].selection).toEqual(null) + expect(FocusFollowingSelectionManager.focused).notToEqualNull() + expect(FocusFollowingSelectionManager.focused?.child).toBeAnInstanceOf() + 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 - 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) - ) + ) { + 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 - 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) - ) + ) { + 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, block: suspend SkikoComposeUiTest.() -> Unit = {} ) = runSkikoComposeUiTest(size = screenSize) { setContent { App( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.onKeyEvent { event -> + FocusFollowingSelectionManager.onKeyEvent(event) + }, rootSelectionManager = FocusFollowingSelectionManager, spacing = 8.dp, game = game, diff --git a/commonUI/src/commonTest/kotlin/ch/dissem/yaep/ui/common/NamesTest.kt b/commonUI/src/commonTest/kotlin/ch/dissem/yaep/ui/common/NamesTest.kt new file mode 100644 index 0000000..d8aacc3 --- /dev/null +++ b/commonUI/src/commonTest/kotlin/ch/dissem/yaep/ui/common/NamesTest.kt @@ -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>) { + runComposeUiTest { + var strings = listOf() + setContent { + strings = items.map { stringResource(it.localName) } + } + expect(strings).toHaveElementsAndAll { + notToBeBlank() + } + } + } +} \ No newline at end of file diff --git a/commonUI/src/commonTest/kotlin/ch/dissem/yaep/ui/common/focus/SelectionManagerTest.kt b/commonUI/src/commonTest/kotlin/ch/dissem/yaep/ui/common/focus/SelectionManagerTest.kt index 7739ee3..e04a9b4 100644 --- a/commonUI/src/commonTest/kotlin/ch/dissem/yaep/ui/common/focus/SelectionManagerTest.kt +++ b/commonUI/src/commonTest/kotlin/ch/dissem/yaep/ui/common/focus/SelectionManagerTest.kt @@ -1,8 +1,6 @@ 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 import ch.tutteli.atrium.api.fluent.en_GB.toEqual import ch.tutteli.atrium.api.verbs.expect @@ -58,10 +56,4 @@ abstract class SelectionManagerTest, M : SelectionManager> { } } - @OptIn(InternalComposeUiApi::class) - fun keyEvent(key: Key, type: KeyEventType = KeyEventType.KeyUp) = KeyEvent( - key = key, - type = type - ) - } \ No newline at end of file diff --git a/commonUI/src/commonTest/kotlin/ch/dissem/yaep/ui/common/focus/keybord utils.kt b/commonUI/src/commonTest/kotlin/ch/dissem/yaep/ui/common/focus/keybord utils.kt new file mode 100644 index 0000000..ecaff3d --- /dev/null +++ b/commonUI/src/commonTest/kotlin/ch/dissem/yaep/ui/common/focus/keybord utils.kt @@ -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 +) \ No newline at end of file