From ff9ec668317390254a0c9a9040d77abc6b7b1a13 Mon Sep 17 00:00:00 2001 From: Tsavoka Date: Tue, 3 Feb 2026 13:51:15 +0300 Subject: [PATCH 1/2] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BD=D0=B0=D0=B2=D0=B8=D0=B3=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=B8=20=D0=BF=D0=BE=20=D0=BF=D1=80=D0=B8=D0=BB=D0=BE?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/lunchtray/LunchTrayScreen.kt | 168 +++++++++++++++++- 1 file changed, 162 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/example/lunchtray/LunchTrayScreen.kt b/app/src/main/java/com/example/lunchtray/LunchTrayScreen.kt index 8392071..979319a 100644 --- a/app/src/main/java/com/example/lunchtray/LunchTrayScreen.kt +++ b/app/src/main/java/com/example/lunchtray/LunchTrayScreen.kt @@ -15,33 +15,189 @@ */ package com.example.lunchtray +import android.annotation.SuppressLint +import androidx.annotation.StringRes +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import com.example.lunchtray.datasource.DataSource +import com.example.lunchtray.ui.AccompanimentMenuScreen +import com.example.lunchtray.ui.CheckoutScreen +import com.example.lunchtray.ui.EntreeMenuScreen import com.example.lunchtray.ui.OrderViewModel +import com.example.lunchtray.ui.SideDishMenuScreen +import com.example.lunchtray.ui.StartOrderScreen -// TODO: Screen enum -// TODO: AppBar +enum class LunchTrayScreen(@StringRes val title: Int) { + Start(title = R.string.app_name), + EntreeMenu(title = R.string.choose_entree), + SideDishMenu(title = R.string.choose_side_dish), + AccompanimentMenu(title = R.string.choose_accompaniment), + Checkout(title = R.string.order_checkout) +} @OptIn(ExperimentalMaterial3Api::class) @Composable -fun LunchTrayApp() { - // TODO: Create Controller and initialization +fun LunchTrayAppBar( + currentScreen: LunchTrayScreen, + canNavigateBack: Boolean, + navigateUp: () -> Unit, + modifier: Modifier = Modifier +) { + CenterAlignedTopAppBar( + title = { Text(stringResource(currentScreen.title)) }, + modifier = modifier, + navigationIcon = { + if (canNavigateBack) { + IconButton(onClick = navigateUp) { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = stringResource(R.string.back_button) + ) + } + } + } + ) +} + +@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") +@Composable +fun LunchTrayApp( + navController: NavHostController = rememberNavController() +) { + // Create Controller and initialization + val backStackEntry by navController.currentBackStackEntryAsState() + val currentScreen = LunchTrayScreen.valueOf( + backStackEntry?.destination?.route ?: LunchTrayScreen.Start.name + ) // Create ViewModel val viewModel: OrderViewModel = viewModel() Scaffold( topBar = { - // TODO: AppBar + LunchTrayAppBar( + currentScreen = currentScreen, + canNavigateBack = navController.previousBackStackEntry != null, + navigateUp = { navController.navigateUp() } + ) } ) { innerPadding -> val uiState by viewModel.uiState.collectAsState() - // TODO: Navigation host + NavHost( + navController = navController, + startDestination = LunchTrayScreen.Start.name, + modifier = Modifier.padding(innerPadding) + ) { + composable(route = LunchTrayScreen.Start.name) { + StartOrderScreen( + onStartOrderButtonClicked = { + navController.navigate(LunchTrayScreen.EntreeMenu.name) + }, + modifier = Modifier + .fillMaxSize() + .padding(dimensionResource(R.dimen.padding_medium)) + ) + } + + composable(route = LunchTrayScreen.EntreeMenu.name) { + EntreeMenuScreen( + options = DataSource.entreeMenuItems, + onCancelButtonClicked = { + cancelOrderAndNavigateToStart(viewModel, navController) + }, + onSelectionChanged = { + viewModel.updateEntree(it) + }, + onNextButtonClicked = { + navController.navigate(LunchTrayScreen.SideDishMenu.name) + }, + modifier = Modifier + .fillMaxSize() + .padding(dimensionResource(R.dimen.padding_medium)) + ) + } + + composable(route = LunchTrayScreen.SideDishMenu.name) { + SideDishMenuScreen( + options = DataSource.sideDishMenuItems, + onCancelButtonClicked = { + cancelOrderAndNavigateToStart(viewModel, navController) + }, + onSelectionChanged = { + viewModel.updateSideDish(it) + }, + onNextButtonClicked = { + navController.navigate(LunchTrayScreen.AccompanimentMenu.name) + }, + modifier = Modifier + .fillMaxSize() + .padding(dimensionResource(R.dimen.padding_medium)) + ) + } + + composable(route = LunchTrayScreen.AccompanimentMenu.name) { + AccompanimentMenuScreen( + options = DataSource.accompanimentMenuItems, + onCancelButtonClicked = { + cancelOrderAndNavigateToStart(viewModel, navController) + }, + onSelectionChanged = { + viewModel.updateAccompaniment(it) + }, + onNextButtonClicked = { + navController.navigate(LunchTrayScreen.Checkout.name) + }, + modifier = Modifier + .fillMaxSize() + .padding(dimensionResource(R.dimen.padding_medium)) + ) + } + + composable(route = LunchTrayScreen.Checkout.name) { + CheckoutScreen( + orderUiState = uiState, + onCancelButtonClicked = { + cancelOrderAndNavigateToStart(viewModel, navController) + }, + onNextButtonClicked = { + navController.popBackStack(LunchTrayScreen.Start.name, inclusive = false) + }, + modifier = Modifier + .fillMaxSize() + .padding(dimensionResource(R.dimen.padding_medium)) + ) + } + } } } + + +private fun cancelOrderAndNavigateToStart( + viewModel: OrderViewModel, + navController: NavHostController +) { + viewModel.resetOrder() + navController.popBackStack(LunchTrayScreen.Start.name, inclusive = false) +} \ No newline at end of file From 99abc2f6afda993ff9c648fb7ba078541533718c Mon Sep 17 00:00:00 2001 From: Tsavoka Date: Wed, 4 Feb 2026 17:48:25 +0300 Subject: [PATCH 2/2] =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D1=8B=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=BD=D0=B0=D0=B2=D0=B8=D0=B3=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 4 + .../lunchtray/test/ComposeRuleExtensions.kt | 12 ++ .../test/LunchTrayScreenNavigationTest.kt | 170 ++++++++++++++++++ .../lunchtray/test/ScreenAssertions.kt | 8 + 4 files changed, 194 insertions(+) create mode 100644 app/src/androidTest/java/com/example/lunchtray/test/ComposeRuleExtensions.kt create mode 100644 app/src/androidTest/java/com/example/lunchtray/test/LunchTrayScreenNavigationTest.kt create mode 100644 app/src/androidTest/java/com/example/lunchtray/test/ScreenAssertions.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1b8be7c..d84f841 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -74,6 +74,10 @@ dependencies { implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7") implementation("androidx.navigation:navigation-compose:2.8.5") + androidTestImplementation(platform("androidx.compose:compose-bom:2023.05.01")) + androidTestImplementation("androidx.compose.ui:ui-test-junit4") + androidTestImplementation("androidx.navigation:navigation-testing:2.6.0") + debugImplementation("androidx.compose.ui:ui-test-manifest") debugImplementation("androidx.compose.ui:ui-tooling") } diff --git a/app/src/androidTest/java/com/example/lunchtray/test/ComposeRuleExtensions.kt b/app/src/androidTest/java/com/example/lunchtray/test/ComposeRuleExtensions.kt new file mode 100644 index 0000000..0020e1d --- /dev/null +++ b/app/src/androidTest/java/com/example/lunchtray/test/ComposeRuleExtensions.kt @@ -0,0 +1,12 @@ +package com.example.lunchtray.test + +import androidx.activity.ComponentActivity +import androidx.annotation.StringRes +import androidx.compose.ui.test.SemanticsNodeInteraction +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.rules.ActivityScenarioRule + +fun AndroidComposeTestRule, A>.onNodeWithStringId( + @StringRes id: Int +): SemanticsNodeInteraction = onNodeWithText(activity.getString(id)) \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/lunchtray/test/LunchTrayScreenNavigationTest.kt b/app/src/androidTest/java/com/example/lunchtray/test/LunchTrayScreenNavigationTest.kt new file mode 100644 index 0000000..b7637da --- /dev/null +++ b/app/src/androidTest/java/com/example/lunchtray/test/LunchTrayScreenNavigationTest.kt @@ -0,0 +1,170 @@ +package com.example.lunchtray.test + +import androidx.activity.ComponentActivity +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.navigation.compose.ComposeNavigator +import androidx.navigation.testing.TestNavHostController +import com.example.lunchtray.LunchTrayApp +import com.example.lunchtray.LunchTrayScreen +import com.example.lunchtray.R +import org.junit.Before +import org.junit.Rule +import org.junit.Test + + +class LunchTrayScreenNavigationTest { + @get:Rule + val composeTestRule = createAndroidComposeRule() + + private lateinit var navController: TestNavHostController + + @Before + fun setupLunchTrayNavHost() { + composeTestRule.setContent { + navController = TestNavHostController(LocalContext.current).apply { + navigatorProvider.addNavigator(ComposeNavigator()) + } + LunchTrayApp(navController = navController) + } + } + + private fun navigateToEntreeMenu() { + composeTestRule.onNodeWithStringId(R.string.start_order) + .performClick() + composeTestRule.onNodeWithText("Cauliflower") + .performClick() + } + + private fun navigateToSideDishMenu() { + navigateToEntreeMenu() + performNavigateNext() + composeTestRule.onNodeWithText("Summer Salad") + .performClick() + } + + private fun navigateToAccompanimentMenu() { + navigateToSideDishMenu() + performNavigateNext() + composeTestRule.onNodeWithText("Lunch Roll") + .performClick() + } + + private fun navigateToCheckout() { + navigateToAccompanimentMenu() + performNavigateNext() + } + + private fun performNavigateUp() { + val backText = composeTestRule.activity.getString(R.string.back_button) + composeTestRule.onNodeWithContentDescription(backText).performClick() + } + + private fun performNavigateNext() { + composeTestRule.onNodeWithText("NEXT") // боже... + .performClick() + } + + private fun performNavigateCancel() { + composeTestRule.onNodeWithText("CANCEL") // чему я позволяю быть... + .performClick() + } + + @Test + fun lunchTrayNavHost_verifyStartDestination() { + navController.assertCurrentRouteName(LunchTrayScreen.Start.name) + } + + @Test + fun lunchTrayNavHost_verifyBackNavigationNotShownOnStartOrderScreen() { + val backText = composeTestRule.activity.getString(R.string.back_button) + composeTestRule.onNodeWithContentDescription(backText).assertDoesNotExist() + } + + // Entree Menu + @Test + fun lunchTrayNavHost_clickStartOrder_navigatesToSelectEntreeMenuScreen() { + navigateToEntreeMenu() + navController.assertCurrentRouteName(LunchTrayScreen.EntreeMenu.name) + } + + @Test + fun lunchTrayNavHost_clickBackOnEntreeMenuScreen_navigatesToStartOrderScreen() { + navigateToEntreeMenu() + performNavigateUp() + navController.assertCurrentRouteName(LunchTrayScreen.Start.name) + } + + @Test + fun lunchTrayNavHost_clickCancelOnEntreeMenuScreen_navigatesToStartOrderScreen() { + navigateToEntreeMenu() + performNavigateCancel() + navController.assertCurrentRouteName(LunchTrayScreen.Start.name) + } + + // Side Dish Menu + @Test + fun lunchTrayNavHost_navigatesToSideDishMenuScreen() { + navigateToSideDishMenu() + navController.assertCurrentRouteName(LunchTrayScreen.SideDishMenu.name) + } + + @Test + fun lunchTrayNavHost_clickBackOnSideDishMenuScreen_navigatesToEntreeMenuScreen() { + navigateToSideDishMenu() + performNavigateUp() + navController.assertCurrentRouteName(LunchTrayScreen.EntreeMenu.name) + } + + @Test + fun lunchTrayNavHost_clickCancelOnSideDishMenuScreen_navigatesToStartOrderScreen() { + navigateToSideDishMenu() + performNavigateCancel() + navController.assertCurrentRouteName(LunchTrayScreen.Start.name) + } + + // Accompaniment Menu + @Test + fun lunchTrayNavHost_navigatesToAccompanimentMenuScreen() { + navigateToAccompanimentMenu() + navController.assertCurrentRouteName(LunchTrayScreen.AccompanimentMenu.name) + } + + @Test + fun lunchTrayNavHost_clickBackOnAccompanimentMenuScreen_navigatesToSideDishMenuScreen() { + navigateToAccompanimentMenu() + performNavigateUp() + navController.assertCurrentRouteName(LunchTrayScreen.SideDishMenu.name) + } + + @Test + fun lunchTrayNavHost_clickCancelOnAccompanimentMenuScreen_navigatesToStartOrderScreen() { + navigateToAccompanimentMenu() + performNavigateCancel() + navController.assertCurrentRouteName(LunchTrayScreen.Start.name) + } + + // Checkout + @Test + fun lunchTrayNavHost_navigatesToCheckout() { + navigateToCheckout() + navController.assertCurrentRouteName(LunchTrayScreen.Checkout.name) + } + + @Test + fun lunchTrayNavHost_clickBackOnCheckoutScreen_navigatesToAccompanimentMenuScreen() { + navigateToCheckout() + performNavigateUp() + navController.assertCurrentRouteName(LunchTrayScreen.AccompanimentMenu.name) + } + + @Test + fun lunchTrayNavHost_clickCancelOnCheckoutScreen_navigatesToStartOrderScreen() { + navigateToCheckout() + performNavigateCancel() + navController.assertCurrentRouteName(LunchTrayScreen.Start.name) + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/lunchtray/test/ScreenAssertions.kt b/app/src/androidTest/java/com/example/lunchtray/test/ScreenAssertions.kt new file mode 100644 index 0000000..aaedc97 --- /dev/null +++ b/app/src/androidTest/java/com/example/lunchtray/test/ScreenAssertions.kt @@ -0,0 +1,8 @@ +package com.example.lunchtray.test + +import androidx.navigation.NavController +import org.junit.Assert.assertEquals + +fun NavController.assertCurrentRouteName(expectedRouteName: String) { + assertEquals(expectedRouteName, currentBackStackEntry?.destination?.route) +}