app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

ShellAccessibilityUiTest.kt (8035B)


      1 package org.harvestcircle.ui.shell
      2 
      3 import androidx.compose.runtime.CompositionLocalProvider
      4 import androidx.compose.runtime.getValue
      5 import androidx.compose.runtime.mutableStateOf
      6 import androidx.compose.runtime.remember
      7 import androidx.compose.runtime.setValue
      8 import androidx.compose.ui.Modifier
      9 import androidx.compose.ui.input.key.Key
     10 import androidx.compose.ui.platform.testTag
     11 import androidx.compose.ui.test.ExperimentalTestApi
     12 import androidx.compose.ui.test.assertCountEquals
     13 import androidx.compose.ui.test.assertHeightIsAtLeast
     14 import androidx.compose.ui.test.assertIsFocused
     15 import androidx.compose.ui.test.onAllNodesWithTag
     16 import androidx.compose.ui.test.onNodeWithTag
     17 import androidx.compose.ui.test.performClick
     18 import androidx.compose.ui.test.performKeyInput
     19 import androidx.compose.ui.test.v2.runComposeUiTest
     20 import androidx.compose.ui.unit.dp
     21 import org.harvestcircle.application.ApplicationLifecycle
     22 import org.harvestcircle.application.ApplicationSnapshot
     23 import org.harvestcircle.application.BuildInfo
     24 import org.harvestcircle.application.FoundationOverlay
     25 import org.harvestcircle.application.HarvestCirclePresenterState
     26 import org.harvestcircle.application.HarvestCircleShellState
     27 import org.harvestcircle.application.OverlayState
     28 import org.harvestcircle.application.SessionLifecycle
     29 import org.harvestcircle.application.ShellFocusTarget
     30 import org.harvestcircle.application.SnapshotRevision
     31 import org.harvestcircle.application.StatusOverlayKey
     32 import org.harvestcircle.designsystem.component.action.HarvestCircleLabeledButton
     33 import org.harvestcircle.identities.ui.HarvestCirclePlatformActions
     34 import org.harvestcircle.identities.ui.HarvestCircleUiActions
     35 import kotlin.test.Test
     36 
     37 @OptIn(ExperimentalTestApi::class)
     38 class ShellAccessibilityUiTest {
     39     @Test
     40     fun sharedActionsMeetTheMinimumTargetAndRouteContentRestoresFocus() =
     41         runComposeUiTest {
     42             setHarvestCircleContent {
     43                 RouteFocusTarget("today", "Today main content") {
     44                     HarvestCircleLabeledButton(
     45                         "Action",
     46                         "Accessible action",
     47                         {},
     48                         Modifier.testTag("accessible-action"),
     49                     )
     50                 }
     51             }
     52 
     53             onNodeWithTag("accessible-action").assertHeightIsAtLeast(32.dp)
     54             onNodeWithTag("route-focus-target").assertIsFocused()
     55         }
     56 
     57     @Test
     58     fun keyboardHostDispatchesModifiedAndEscapeShortcuts() =
     59         runComposeUiTest {
     60             val shortcuts = mutableListOf<ShellShortcut>()
     61             setHarvestCircleContent {
     62                 ShellKeyboardHost(onShortcut = shortcuts::add) {
     63                     RouteFocusTarget("today", "Today main content", content = {})
     64                 }
     65             }
     66 
     67             onNodeWithTag("route-focus-target").performKeyInput {
     68                 keyDown(Key.CtrlLeft)
     69                 keyDown(Key.K)
     70                 keyUp(Key.K)
     71                 keyUp(Key.CtrlLeft)
     72                 keyDown(Key.Escape)
     73                 keyUp(Key.Escape)
     74             }
     75             kotlin.test.assertEquals(
     76                 listOf(ShellShortcut.OpenNostrReference, ShellShortcut.CloseOverlay),
     77                 shortcuts,
     78             )
     79         }
     80 
     81     @Test
     82     fun keyboardHostSuppressesBackgroundShortcutsWhileModalIsOpen() =
     83         runComposeUiTest {
     84             val shortcuts = mutableListOf<ShellShortcut>()
     85             setHarvestCircleContent {
     86                 ShellKeyboardHost(
     87                     modal = FoundationOverlay.Status(StatusOverlayKey.Sync),
     88                     onShortcut = shortcuts::add,
     89                 ) {
     90                     RouteFocusTarget("today", "Today main content", content = {})
     91                 }
     92             }
     93 
     94             onNodeWithTag("route-focus-target").performKeyInput {
     95                 keyDown(Key.CtrlLeft)
     96                 keyDown(Key.K)
     97                 keyUp(Key.K)
     98                 keyUp(Key.CtrlLeft)
     99                 keyDown(Key.Escape)
    100                 keyUp(Key.Escape)
    101             }
    102             kotlin.test.assertEquals(listOf(ShellShortcut.CloseOverlay), shortcuts)
    103         }
    104 
    105     @Test
    106     fun hcSc011ModalRemovesTheBackgroundSemanticsSubtree() =
    107         runComposeUiTest {
    108             setHarvestCircleContent {
    109                 HarvestCircleShell(
    110                     state = modalBootstrapState(),
    111                     identityActions = HarvestCircleUiActions(),
    112                     platformActions = HarvestCirclePlatformActions(),
    113                     dispatch = {},
    114                 )
    115             }
    116 
    117             onAllNodesWithTag("bootstrap-welcome").assertCountEquals(0)
    118             onAllNodesWithTag("foundation-overlay").assertCountEquals(1)
    119         }
    120 
    121     @Test
    122     fun routeTargetRestoresFocusAfterAClosedModal() =
    123         runComposeUiTest {
    124             var modalOpen by mutableStateOf(false)
    125             var restoreTarget by mutableStateOf<ShellFocusTarget?>(null)
    126             setHarvestCircleContent {
    127                 val registry = remember { ShellFocusRegistry() }
    128                 CompositionLocalProvider(LocalShellFocusRegistry provides registry) {
    129                     RouteFocusTarget("today", "Today main content") {
    130                         HarvestCircleLabeledButton(
    131                             label = "Open",
    132                             accessibilityLabel = "Open dialog",
    133                             modifier =
    134                                 Modifier
    135                                     .shellFocusTarget(ShellFocusTarget.TodayReference)
    136                                     .testTag("restore-trigger"),
    137                             onClick = {
    138                                 restoreTarget = null
    139                                 modalOpen = true
    140                             },
    141                         )
    142                         if (modalOpen) {
    143                             FoundationOverlayHost(
    144                                 org.harvestcircle.application.OverlayState(
    145                                     org.harvestcircle.application.FoundationOverlay.Status(
    146                                         org.harvestcircle.application.StatusOverlayKey.Sync,
    147                                     ),
    148                                 ),
    149                                 org.harvestcircle.application.ShellStatusModel(
    150                                     org.harvestcircle.application.SyncStatusLabel.NotYetObserved,
    151                                     org.harvestcircle.application.SignerStatusLabel.SignedOut,
    152                                     null,
    153                                 ),
    154                             ) {
    155                                 modalOpen = false
    156                                 restoreTarget = ShellFocusTarget.TodayReference
    157                             }
    158                         }
    159                     }
    160                     ShellFocusRestorer(restoreTarget, ShellFocusTarget.RouteFallback)
    161                 }
    162             }
    163 
    164             onNodeWithTag("restore-trigger").performClick()
    165             onNodeWithTag("overlay-close").assertIsFocused().performClick()
    166             onNodeWithTag("restore-trigger").assertIsFocused()
    167         }
    168 }
    169 
    170 private fun modalBootstrapState(): HarvestCircleShellState =
    171     HarvestCircleShellState(
    172         identity =
    173             HarvestCirclePresenterState(
    174                 ApplicationSnapshot(
    175                     revision = SnapshotRevision(1UL),
    176                     lifecycle = ApplicationLifecycle.Ready,
    177                     lifecycleProblem = null,
    178                     configuredRelays = emptyList(),
    179                     identities = emptyList(),
    180                     selectedIdentityId = null,
    181                     session = SessionLifecycle.SignedOut,
    182                     sessionSubjectIdentityId = null,
    183                     sessionProblem = null,
    184                     activeIdentity = null,
    185                     recoverableProblem = null,
    186                 ),
    187             ),
    188         buildInfo = BuildInfo.unknown(),
    189         overlays = OverlayState(current = FoundationOverlay.Status(StatusOverlayKey.Sync)),
    190     )